288
README.md
288
README.md
@@ -145,140 +145,180 @@ You can also use WPS to connect your 2.4GHz-only device to force the repeater to
|
|||||||
_a.k.a. things that can go wrong if this app doesn't work._
|
_a.k.a. things that can go wrong if this app doesn't work._
|
||||||
|
|
||||||
This is a list of stuff that might impact this app's functionality if unavailable.
|
This is a list of stuff that might impact this app's functionality if unavailable.
|
||||||
This is only meant to be an index. You can read more in the source code.
|
This is only meant to be an index.
|
||||||
|
You can read more in the source code.
|
||||||
|
API restrictions are updated up to [SHA-256 checksum `156715dfa705a048926dca876d731d72604df32e8bcac055af32866b50bc2cc8`](https://dl.google.com/developers/android/sc/non-sdk/hiddenapi-flags.csv).
|
||||||
|
|
||||||
Greylisted/blacklisted APIs or internal constants: (some constants are hardcoded or implicitly used)
|
Greylisted/blacklisted APIs or internal constants: (some constants are hardcoded or implicitly used)
|
||||||
|
|
||||||
* [`Landroid/net/ConnectivityManager;->getLastTetherError(Ljava/lang/String;)I,greylist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#144306)
|
* (prior to API 30) `Landroid/net/ConnectivityManager;->getLastTetherError(Ljava/lang/String;)I,max-target-r`
|
||||||
* (since API 30) `Landroid/net/ConnectivityModuleConnector;->IN_PROCESS_SUFFIX:Ljava/lang/String;`
|
* (since API 30) `Landroid/net/ConnectivityModuleConnector;->IN_PROCESS_SUFFIX:Ljava/lang/String;`
|
||||||
* (since API 30) [`Landroid/net/TetheringManager$TetheringEventCallback;->onTetherableInterfaceRegexpsChanged(Landroid/net/TetheringManager$TetheringInterfaceRegexps;)V,blacklist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#148899)
|
* (since API 30) `Landroid/net/TetheringManager$TetheringEventCallback;->onTetherableInterfaceRegexpsChanged(Landroid/net/TetheringManager$TetheringInterfaceRegexps;)V,blocked`
|
||||||
* (since API 30) `Landroid/net/TetheringManager;->TETHERING_WIGIG:I`
|
* (since API 30) `Landroid/net/TetheringManager;->TETHERING_WIGIG:I,blocked`
|
||||||
* (prior to API 30) [`Landroid/net/wifi/WifiConfiguration$KeyMgmt;->FT_PSK:I,greylist-max-o`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#153923)
|
* (since API 31) `Landroid/net/wifi/SoftApConfiguration$Builder;->setUserConfiguration(Z)Landroid/net/wifi/SoftApConfiguration$Builder;,blocked`
|
||||||
* (prior to API 30) [`Landroid/net/wifi/WifiConfiguration$KeyMgmt;->WPA_PSK_SHA256:I,blacklist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#153936)
|
* (since API 31) `Landroid/net/wifi/SoftApConfiguration;->BAND_TYPES:[I,blocked`
|
||||||
* (since API 23, prior to API 30) [`Landroid/net/wifi/WifiConfiguration;->AP_BAND_2GHZ:I,greylist-max-o`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#154057)
|
* (since API 31) `Landroid/net/wifi/SoftApInfo;->getApInstanceIdentifier()Ljava/lang/String;,blocked`
|
||||||
* (since API 23, prior to API 30) [`Landroid/net/wifi/WifiConfiguration;->AP_BAND_5GHZ:I,greylist-max-o`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#154058)
|
* (since API 31) `Landroid/net/wifi/WifiClient;->getApInstanceIdentifier()Ljava/lang/String;,blocked`
|
||||||
* (since API 23, prior to API 30) [`Landroid/net/wifi/WifiConfiguration;->AP_BAND_ANY:I,greylist-max-o`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#154059)
|
* (prior to API 30) `Landroid/net/wifi/WifiConfiguration$KeyMgmt;->FT_PSK:I,lo-prio,max-target-o`
|
||||||
* (since API 23, prior to API 30) [`Landroid/net/wifi/WifiConfiguration;->apBand:I,greylist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#154101)
|
* (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;->apChannel:I,greylist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#154102)
|
* (since API 23, prior to API 30) `Landroid/net/wifi/WifiConfiguration;->AP_BAND_2GHZ:I,lo-prio,max-target-o`
|
||||||
* (since API 28, prior to API 30) [`Landroid/net/wifi/WifiManager$SoftApCallback;->onNumClientsChanged(I)V,greylist-max-o`](https://android.googlesource.com/platform/prebuilts/runtime/+/3d07e5c/appcompat/hiddenapi-flags.csv#132005)
|
* (since API 23, prior to API 30) `Landroid/net/wifi/WifiConfiguration;->AP_BAND_5GHZ:I,lo-prio,max-target-o`
|
||||||
* (since API 26) [`Landroid/net/wifi/WifiManager;->cancelLocalOnlyHotspotRequest()V,greylist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#154947s)
|
* (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 26) `Landroid/net/wifi/WifiManager;->setWifiApEnabled(Landroid/net/wifi/WifiConfiguration;Z)Z`
|
||||||
* [`Landroid/net/wifi/p2p/WifiP2pConfig$Builder;->MAC_ANY_ADDRESS:Landroid/net/MacAddress;,blacklist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#157883)
|
* `Landroid/net/wifi/p2p/WifiP2pConfig$Builder;->MAC_ANY_ADDRESS:Landroid/net/MacAddress;,blocked`
|
||||||
* [`Landroid/net/wifi/p2p/WifiP2pManager;->startWps(Landroid/net/wifi/p2p/WifiP2pManager$Channel;Landroid/net/wifi/WpsInfo;Landroid/net/wifi/p2p/WifiP2pManager$ActionListener;)V,greylist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#158332)
|
* (since API 29) `Landroid/net/wifi/p2p/WifiP2pConfig$Builder;->mNetworkName:Ljava/lang/String;,blocked`
|
||||||
* (since API 28) [`Landroid/provider/Settings$Global;->SOFT_AP_TIMEOUT_ENABLED:Ljava/lang/String;,greylist-max-o`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#183735)
|
* `Landroid/net/wifi/p2p/WifiP2pManager;->startWps(Landroid/net/wifi/p2p/WifiP2pManager$Channel;Landroid/net/wifi/WpsInfo;Landroid/net/wifi/p2p/WifiP2pManager$ActionListener;)V,max-target-r`
|
||||||
* (prior to API 30) [`Lcom/android/internal/R$array;->config_tether_bluetooth_regexs:I,greylist-max-q`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#272546)
|
* (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) [`Lcom/android/internal/R$array;->config_tether_usb_regexs:I,greylist-max-q`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#272549)
|
* (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_wifi_regexs:I,greylist-max-q`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#272551)
|
* (prior to API 30) `Lcom/android/internal/R$array;->config_tether_usb_regexs:I,max-target-q`
|
||||||
* (since API 28, prior to API 30) [`Lcom/android/internal/R$integer;->config_wifi_framework_soft_ap_timeout_delay:I,greylist-max-o`](https://android.googlesource.com/platform/prebuilts/runtime/+/3d07e5c/appcompat/hiddenapi-flags.csv#245007)
|
* (prior to API 30) `Lcom/android/internal/R$array;->config_tether_wifi_regexs:I,max-target-q`
|
||||||
* [`Lcom/android/internal/R$string;->config_ethernet_iface_regex:I,greylist-max-o`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#276573)
|
* (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`
|
||||||
|
* `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 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 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 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 30) `Lcom/android/server/SystemServer;->TETHERING_CONNECTOR_CLASS:Ljava/lang/String;`
|
||||||
* (since API 26) [`Ljava/lang/invoke/MethodHandles$Lookup;-><init>(Ljava/lang/Class;I)V,greylist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#370415)
|
* (since API 29) `Ldalvik/system/VMRuntime;->getRuntime()Ldalvik/system/VMRuntime;,core-platform-api,unsupported`
|
||||||
* (since API 26) [`Ljava/lang/invoke/MethodHandles$Lookup;->ALL_MODES:I,greylist-max-o`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#370416)
|
* (since API 29) `Ldalvik/system/VMRuntime;->setHiddenApiExemptions([Ljava/lang/String;)V,blocked,core-platform-api`
|
||||||
* (prior to API 29) [`Ljava/net/InetAddress;->parseNumericAddress(Ljava/lang/String;)Ljava/net/InetAddress;,core-platform-api,greylist-max-p`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#372578)
|
* (since API 26) `Ljava/lang/invoke/MethodHandles$Lookup;-><init>(Ljava/lang/Class;I)V,unsupported`
|
||||||
|
* (since API 26) `Ljava/lang/invoke/MethodHandles$Lookup;->ALL_MODES:I,lo-prio,max-target-o`
|
||||||
|
* (prior to API 29) `Ljava/net/InetAddress;->parseNumericAddress(Ljava/lang/String;)Ljava/net/InetAddress;,core-platform-api,max-target-p`
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Hidden whitelisted APIs: (same catch as above, however, things in this list are less likely to be broken)</summary>
|
<summary>Hidden whitelisted APIs: (same catch as above, however, things in this list are less likely to be broken)</summary>
|
||||||
|
|
||||||
* (since API 24) [`Landroid/bluetooth/BluetoothPan;->isTetheringOn()Z,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#35264)
|
* (since API 24) `Landroid/bluetooth/BluetoothPan;->isTetheringOn()Z,sdk,system-api,test-api`
|
||||||
* (since API 24) [`Landroid/bluetooth/BluetoothProfile;->PAN:I,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#35361)
|
* (since API 24) `Landroid/bluetooth/BluetoothProfile;->PAN:I,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/content/Context;->TETHERING_SERVICE:Ljava/lang/String;,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#41038)
|
* (since API 30) `Landroid/content/Context;->TETHERING_SERVICE:Ljava/lang/String;,sdk,system-api,test-api`
|
||||||
* (since API 24, prior to API 30) [`Landroid/net/ConnectivityManager$OnStartTetheringCallback;-><init>()V,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#144095)
|
* (since API 24, prior to API 30) `Landroid/net/ConnectivityManager$OnStartTetheringCallback;-><init>()V,sdk,system-api,test-api`
|
||||||
* (since API 24, prior to API 30) [`Landroid/net/ConnectivityManager$OnStartTetheringCallback;->onTetheringFailed()V,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#144096)
|
* (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,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#144097)
|
* (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,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#144406)
|
* (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,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#144408)
|
* (since API 24, prior to API 30) `Landroid/net/ConnectivityManager;->stopTethering(I)V,sdk,system-api,test-api`
|
||||||
* [`Landroid/net/LinkProperties;->getAllInterfaceNames()Ljava/util/List;,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#146558)
|
* `Landroid/net/LinkProperties;->getAllInterfaceNames()Ljava/util/List;,sdk,system-api,test-api`
|
||||||
* [`Landroid/net/LinkProperties;->getAllRoutes()Ljava/util/List;,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#146560)
|
* `Landroid/net/LinkProperties;->getAllRoutes()Ljava/util/List;,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/TetheringManager$StartTetheringCallback;->onTetheringFailed(I)V,system-api,test-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#148881)
|
* (since API 30) `Landroid/net/TetheringManager$StartTetheringCallback;->onTetheringFailed(I)V,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/TetheringManager$StartTetheringCallback;->onTetheringStarted()V,system-api,test-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#148882)
|
* (since API 30) `Landroid/net/TetheringManager$StartTetheringCallback;->onTetheringStarted()V,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/TetheringManager$TetheringEventCallback;->onClientsChanged(Ljava/util/Collection;)V,system-api,test-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#148896)
|
* (since API 30) `Landroid/net/TetheringManager$TetheringEventCallback;->onClientsChanged(Ljava/util/Collection;)V,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/TetheringManager$TetheringEventCallback;->onError(Ljava/lang/String;I)V,system-api,test-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#148897)
|
* (since API 30) `Landroid/net/TetheringManager$TetheringEventCallback;->onError(Ljava/lang/String;I)V,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/TetheringManager$TetheringEventCallback;->onOffloadStatusChanged(I)V,system-api,test-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#148898)
|
* (since API 30) `Landroid/net/TetheringManager$TetheringEventCallback;->onOffloadStatusChanged(I)V,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/TetheringManager$TetheringEventCallback;->onTetherableInterfacesChanged(Ljava/util/List;)V,system-api,test-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#148900)
|
* (since API 30) `Landroid/net/TetheringManager$TetheringEventCallback;->onTetherableInterfacesChanged(Ljava/util/List;)V,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/TetheringManager$TetheringEventCallback;->onTetheredInterfacesChanged(Ljava/util/List;)V,system-api,test-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#148901)
|
* (since API 30) `Landroid/net/TetheringManager$TetheringEventCallback;->onTetheredInterfacesChanged(Ljava/util/List;)V,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/TetheringManager$TetheringEventCallback;->onTetheringSupported(Z)V,system-api,test-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#148902)
|
* (since API 30) `Landroid/net/TetheringManager$TetheringEventCallback;->onTetheringSupported(Z)V,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/TetheringManager$TetheringEventCallback;->onUpstreamChanged(Landroid/net/Network;)V,system-api,test-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#148903)
|
* (since API 30) `Landroid/net/TetheringManager$TetheringEventCallback;->onUpstreamChanged(Landroid/net/Network;)V,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/TetheringManager$TetheringRequest$Builder;-><init>(I)V,system-api,test-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#148913)
|
* (since API 30) `Landroid/net/TetheringManager$TetheringRequest$Builder;-><init>(I)V,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/TetheringManager$TetheringRequest$Builder;->build()Landroid/net/TetheringManager$TetheringRequest;,system-api,test-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#148914)
|
* (since API 30) `Landroid/net/TetheringManager$TetheringRequest$Builder;->build()Landroid/net/TetheringManager$TetheringRequest;,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/TetheringManager$TetheringRequest$Builder;->setExemptFromEntitlementCheck(Z)Landroid/net/TetheringManager$TetheringRequest$Builder;,system-api,test-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#148916)
|
* (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;,system-api,test-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#148917)
|
* (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;,system-api,test-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#148932)
|
* `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;,system-api,test-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#148935)
|
* (since API 26) `Landroid/net/TetheringManager;->EXTRA_ACTIVE_LOCAL_ONLY:Ljava/lang/String;,sdk,system-api,test-api`
|
||||||
* [`Landroid/net/TetheringManager;->EXTRA_ACTIVE_TETHER:Ljava/lang/String;,system-api,test-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#148936)
|
* `Landroid/net/TetheringManager;->EXTRA_ACTIVE_TETHER:Ljava/lang/String;,sdk,system-api,test-api`
|
||||||
* [`Landroid/net/TetheringManager;->EXTRA_ERRORED_TETHER:Ljava/lang/String;,system-api,test-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#148938)
|
* `Landroid/net/TetheringManager;->EXTRA_ERRORED_TETHER:Ljava/lang/String;,sdk,system-api,test-api`
|
||||||
* (since API 24) [`Landroid/net/TetheringManager;->TETHERING_BLUETOOTH:I,system-api,test-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#148940)
|
* (since API 24) `Landroid/net/TetheringManager;->TETHERING_BLUETOOTH:I,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/TetheringManager;->TETHERING_ETHERNET:I,system-api,test-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#148941)
|
* (since API 30) `Landroid/net/TetheringManager;->TETHERING_ETHERNET:I,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/TetheringManager;->TETHERING_NCM:I,system-api,test-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#148943)
|
* (since API 30) `Landroid/net/TetheringManager;->TETHERING_NCM:I,sdk,system-api,test-api`
|
||||||
* (since API 24) [`Landroid/net/TetheringManager;->TETHERING_USB:I,system-api,test-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#148944)
|
* (since API 24) `Landroid/net/TetheringManager;->TETHERING_USB:I,sdk,system-api,test-api`
|
||||||
* (since API 24) [`Landroid/net/TetheringManager;->TETHERING_WIFI:I,system-api,test-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#148945)
|
* (since API 24) `Landroid/net/TetheringManager;->TETHERING_WIFI:I,sdk,system-api,test-api`
|
||||||
* [`Landroid/net/TetheringManager;->TETHER_ERROR_*:I,system-api,test-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#148947)
|
* `Landroid/net/TetheringManager;->TETHER_ERROR_*:I,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/TetheringManager;->TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION:I,system-api,test-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#148954)
|
* (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,system-api,test-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#148964)
|
* (since API 30) `Landroid/net/TetheringManager;->TETHER_HARDWARE_OFFLOAD_FAILED:I,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/TetheringManager;->TETHER_HARDWARE_OFFLOAD_STARTED:I,system-api,test-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#148965)
|
* (since API 30) `Landroid/net/TetheringManager;->TETHER_HARDWARE_OFFLOAD_STARTED:I,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/TetheringManager;->TETHER_HARDWARE_OFFLOAD_STOPPED:I,system-api,test-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#148966)
|
* (since API 30) `Landroid/net/TetheringManager;->TETHER_HARDWARE_OFFLOAD_STOPPED:I,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/TetheringManager;->registerTetheringEventCallback(Ljava/util/concurrent/Executor;Landroid/net/TetheringManager$TetheringEventCallback;)V,system-api,test-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#149003)
|
* (since API 30) `Landroid/net/TetheringManager;->registerTetheringEventCallback(Ljava/util/concurrent/Executor;Landroid/net/TetheringManager$TetheringEventCallback;)V,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/TetheringManager;->startTethering(Landroid/net/TetheringManager$TetheringRequest;Ljava/util/concurrent/Executor;Landroid/net/TetheringManager$StartTetheringCallback;)V,system-api,test-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#149009)
|
* (since API 30) `Landroid/net/TetheringManager;->startTethering(Landroid/net/TetheringManager$TetheringRequest;Ljava/util/concurrent/Executor;Landroid/net/TetheringManager$StartTetheringCallback;)V,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/TetheringManager;->stopTethering(I)V,system-api,test-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#149011)
|
* (since API 30) `Landroid/net/TetheringManager;->stopTethering(I)V,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/TetheringManager;->unregisterTetheringEventCallback(Landroid/net/TetheringManager$TetheringEventCallback;)V,system-api,test-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#149014)
|
* (since API 30) `Landroid/net/TetheringManager;->unregisterTetheringEventCallback(Landroid/net/TetheringManager$TetheringEventCallback;)V,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/wifi/SoftApCapability;->SOFTAP_FEATURE_*:J,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#153656)
|
* (since API 31) `Landroid/net/wifi/SoftApCapability;->SOFTAP_FEATURE_BAND_24G_SUPPORTED:J,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/wifi/SoftApCapability;->areFeaturesSupported(J)Z,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#153660)
|
* (since API 31) `Landroid/net/wifi/SoftApCapability;->SOFTAP_FEATURE_BAND_5G_SUPPORTED:J,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/wifi/SoftApCapability;->getMaxSupportedClients()I,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#153663)
|
* (since API 31) `Landroid/net/wifi/SoftApCapability;->SOFTAP_FEATURE_BAND_60G_SUPPORTED:J,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/wifi/SoftApConfiguration$Builder;-><init>()V,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#153707)
|
* (since API 31) `Landroid/net/wifi/SoftApCapability;->SOFTAP_FEATURE_BAND_6G_SUPPORTED:J,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/wifi/SoftApConfiguration$Builder;-><init>(Landroid/net/wifi/SoftApConfiguration;)V,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#153708)
|
* (since API 30) `Landroid/net/wifi/SoftApCapability;->SOFTAP_FEATURE_*:J,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/wifi/SoftApConfiguration$Builder;->build()Landroid/net/wifi/SoftApConfiguration;,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#153709)
|
* (since API 30) `Landroid/net/wifi/SoftApCapability;->areFeaturesSupported(J)Z,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/wifi/SoftApConfiguration$Builder;->setAllowedClientList(Ljava/util/List;)Landroid/net/wifi/SoftApConfiguration$Builder;,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#153723)
|
* (since API 30) `Landroid/net/wifi/SoftApCapability;->getMaxSupportedClients()I,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/wifi/SoftApConfiguration$Builder;->setAutoShutdownEnabled(Z)Landroid/net/wifi/SoftApConfiguration$Builder;,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#153724)
|
* (since API 31) `Landroid/net/wifi/SoftApCapability;->getSupportedChannelList(I)[I,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/wifi/SoftApConfiguration$Builder;->setBand(I)Landroid/net/wifi/SoftApConfiguration$Builder;,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#153725)
|
* (since API 30) `Landroid/net/wifi/SoftApConfiguration$Builder;-><init>()V,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/wifi/SoftApConfiguration$Builder;->setBlockedClientList(Ljava/util/List;)Landroid/net/wifi/SoftApConfiguration$Builder;,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#153726)
|
* (since API 30) `Landroid/net/wifi/SoftApConfiguration$Builder;-><init>(Landroid/net/wifi/SoftApConfiguration;)V,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/wifi/SoftApConfiguration$Builder;->setBssid(Landroid/net/MacAddress;)Landroid/net/wifi/SoftApConfiguration$Builder;,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#153727)
|
* (since API 30) `Landroid/net/wifi/SoftApConfiguration$Builder;->build()Landroid/net/wifi/SoftApConfiguration;,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/wifi/SoftApConfiguration$Builder;->setChannel(II)Landroid/net/wifi/SoftApConfiguration$Builder;,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#153728)
|
* (since API 30) `Landroid/net/wifi/SoftApConfiguration$Builder;->setAllowedClientList(Ljava/util/List;)Landroid/net/wifi/SoftApConfiguration$Builder;,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/wifi/SoftApConfiguration$Builder;->setClientControlByUserEnabled(Z)Landroid/net/wifi/SoftApConfiguration$Builder;,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#153729)
|
* (since API 30) `Landroid/net/wifi/SoftApConfiguration$Builder;->setAutoShutdownEnabled(Z)Landroid/net/wifi/SoftApConfiguration$Builder;,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/wifi/SoftApConfiguration$Builder;->setHiddenSsid(Z)Landroid/net/wifi/SoftApConfiguration$Builder;,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#153730)
|
* (on API 30) `Landroid/net/wifi/SoftApConfiguration$Builder;->setBand(I)Landroid/net/wifi/SoftApConfiguration$Builder;,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/wifi/SoftApConfiguration$Builder;->setMaxNumberOfClients(I)Landroid/net/wifi/SoftApConfiguration$Builder;,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#153731)
|
* (since API 30) `Landroid/net/wifi/SoftApConfiguration$Builder;->setBlockedClientList(Ljava/util/List;)Landroid/net/wifi/SoftApConfiguration$Builder;,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/wifi/SoftApConfiguration$Builder;->setPassphrase(Ljava/lang/String;I)Landroid/net/wifi/SoftApConfiguration$Builder;,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#153732)
|
* (since API 31) `Landroid/net/wifi/SoftApConfiguration$Builder;->setBridgedModeOpportunisticShutdownEnabled(Z)Landroid/net/wifi/SoftApConfiguration$Builder;,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/wifi/SoftApConfiguration$Builder;->setShutdownTimeoutMillis(J)Landroid/net/wifi/SoftApConfiguration$Builder;,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#153733)
|
* (since API 30) `Landroid/net/wifi/SoftApConfiguration$Builder;->setBssid(Landroid/net/MacAddress;)Landroid/net/wifi/SoftApConfiguration$Builder;,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/wifi/SoftApConfiguration$Builder;->setSsid(Ljava/lang/String;)Landroid/net/wifi/SoftApConfiguration$Builder;,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#153734)
|
* (on API 30) `Landroid/net/wifi/SoftApConfiguration$Builder;->setChannel(II)Landroid/net/wifi/SoftApConfiguration$Builder;,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/wifi/SoftApConfiguration;->BAND_2GHZ:I,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#153738)
|
* (since API 31) `Landroid/net/wifi/SoftApConfiguration$Builder;->setChannels(Landroid/util/SparseIntArray;)Landroid/net/wifi/SoftApConfiguration$Builder;,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/wifi/SoftApConfiguration;->BAND_5GHZ:I,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#153739)
|
* (since API 30) `Landroid/net/wifi/SoftApConfiguration$Builder;->setClientControlByUserEnabled(Z)Landroid/net/wifi/SoftApConfiguration$Builder;,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/wifi/SoftApConfiguration;->BAND_6GHZ:I,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#153740)
|
* (since API 30) `Landroid/net/wifi/SoftApConfiguration$Builder;->setHiddenSsid(Z)Landroid/net/wifi/SoftApConfiguration$Builder;,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/wifi/SoftApConfiguration;->BAND_ANY:I,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#153741)
|
* (since API 31) `Landroid/net/wifi/SoftApConfiguration$Builder;->setIeee80211axEnabled(Z)Landroid/net/wifi/SoftApConfiguration$Builder;,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/wifi/SoftApConfiguration;->getAllowedClientList()Ljava/util/List;,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#153773)
|
* (since API 31) `Landroid/net/wifi/SoftApConfiguration$Builder;->setMacRandomizationSetting(I)Landroid/net/wifi/SoftApConfiguration$Builder;,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/wifi/SoftApConfiguration;->getBand()I,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#153774)
|
* (since API 30) `Landroid/net/wifi/SoftApConfiguration$Builder;->setMaxNumberOfClients(I)Landroid/net/wifi/SoftApConfiguration$Builder;,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/wifi/SoftApConfiguration;->getBlockedClientList()Ljava/util/List;,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#153775)
|
* (since API 30) `Landroid/net/wifi/SoftApConfiguration$Builder;->setPassphrase(Ljava/lang/String;I)Landroid/net/wifi/SoftApConfiguration$Builder;,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/wifi/SoftApConfiguration;->getChannel()I,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#153777)
|
* (since API 30) `Landroid/net/wifi/SoftApConfiguration$Builder;->setShutdownTimeoutMillis(J)Landroid/net/wifi/SoftApConfiguration$Builder;,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/wifi/SoftApConfiguration;->getMaxNumberOfClients()I,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#153778)
|
* (since API 30) `Landroid/net/wifi/SoftApConfiguration$Builder;->setSsid(Ljava/lang/String;)Landroid/net/wifi/SoftApConfiguration$Builder;,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/wifi/SoftApConfiguration;->getShutdownTimeoutMillis()J,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#153781)
|
* (since API 30) `Landroid/net/wifi/SoftApConfiguration;->BAND_2GHZ:I,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/wifi/SoftApConfiguration;->isAutoShutdownEnabled()Z,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#153784)
|
* (since API 30) `Landroid/net/wifi/SoftApConfiguration;->BAND_5GHZ:I,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/wifi/SoftApConfiguration;->isClientControlByUserEnabled()Z,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#153787)
|
* (since API 31) `Landroid/net/wifi/SoftApConfiguration;->BAND_60GHZ:I,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/wifi/SoftApInfo;->CHANNEL_WIDTH_*:I,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#153813)
|
* (since API 30) `Landroid/net/wifi/SoftApConfiguration;->BAND_6GHZ:I,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/wifi/SoftApInfo;->CHANNEL_WIDTH_INVALID:I,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#153819)
|
* (since API 31) `Landroid/net/wifi/SoftApConfiguration;->BAND_*:I,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/wifi/SoftApInfo;->getBandwidth()I,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#153825)
|
* (since API 31) `Landroid/net/wifi/SoftApConfiguration;->RANDOMIZATION_NONE:I,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/wifi/SoftApInfo;->getFrequency()I,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#153826)
|
* (since API 31) `Landroid/net/wifi/SoftApConfiguration;->RANDOMIZATION_PERSISTENT:I,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/wifi/WifiClient;->getMacAddress()Landroid/net/MacAddress;,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#153881)
|
* (since API 30) `Landroid/net/wifi/SoftApConfiguration;->getAllowedClientList()Ljava/util/List;,sdk,system-api,test-api`
|
||||||
* (prior to API 30) [`Landroid/net/wifi/WifiConfiguration$KeyMgmt;->WPA2_PSK:I,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#153932)
|
* (since API 30) `Landroid/net/wifi/SoftApConfiguration;->getBand()I,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/wifi/WifiManager$SoftApCallback;->onBlockedClientConnecting(Landroid/net/wifi/WifiClient;I)V,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#154681)
|
* (since API 30) `Landroid/net/wifi/SoftApConfiguration;->getBlockedClientList()Ljava/util/List;,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/wifi/WifiManager$SoftApCallback;->onCapabilityChanged(Landroid/net/wifi/SoftApCapability;)V,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#154682)
|
* (since API 30) `Landroid/net/wifi/SoftApConfiguration;->getChannel()I,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/wifi/WifiManager$SoftApCallback;->onConnectedClientsChanged(Ljava/util/List;)V,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#154683)
|
* (since API 31) `Landroid/net/wifi/SoftApConfiguration;->getChannels()Landroid/util/SparseIntArray;,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/wifi/WifiManager$SoftApCallback;->onInfoChanged(Landroid/net/wifi/SoftApInfo;)V,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#154684)
|
* (since API 31) `Landroid/net/wifi/SoftApConfiguration;->getMacRandomizationSetting()I,sdk,system-api,test-api`
|
||||||
* (since API 28) [`Landroid/net/wifi/WifiManager$SoftApCallback;->onStateChanged(II)V,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#154685)
|
* (since API 30) `Landroid/net/wifi/SoftApConfiguration;->getMaxNumberOfClients()I,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/wifi/WifiManager;->SAP_CLIENT_BLOCK_REASON_CODE_*:I,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#154840)
|
* (since API 30) `Landroid/net/wifi/SoftApConfiguration;->getShutdownTimeoutMillis()J,sdk,system-api,test-api`
|
||||||
* (since API 28) [`Landroid/net/wifi/WifiManager;->SAP_START_FAILURE_*:I,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#154843)
|
* (since API 30) `Landroid/net/wifi/SoftApConfiguration;->isAutoShutdownEnabled()Z,sdk,system-api,test-api`
|
||||||
* (since API 28) [`Landroid/net/wifi/WifiManager;->WIFI_AP_STATE_FAILED:I,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#154868)
|
* (since API 31) `Landroid/net/wifi/SoftApConfiguration;->isBridgedModeOpportunisticShutdownEnabled()Z,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/wifi/WifiManager;->getSoftApConfiguration()Landroid/net/wifi/SoftApConfiguration;,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#154985)
|
* (since API 30) `Landroid/net/wifi/SoftApConfiguration;->isClientControlByUserEnabled()Z,sdk,system-api,test-api`
|
||||||
* (prior to API 30) [`Landroid/net/wifi/WifiManager;->getWifiApConfiguration()Landroid/net/wifi/WifiConfiguration;,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#154989)
|
* (since API 31) `Landroid/net/wifi/SoftApConfiguration;->isIeee80211axEnabled()Z,sdk,system-api,test-api`
|
||||||
* (since API 28) [`Landroid/net/wifi/WifiManager;->registerSoftApCallback(Ljava/util/concurrent/Executor;Landroid/net/wifi/WifiManager$SoftApCallback;)V,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#155042)
|
* (since API 31) `Landroid/net/wifi/SoftApConfiguration;->isUserConfiguration()Z,sdk,system-api,test-api`
|
||||||
* (since API 30) [`Landroid/net/wifi/WifiManager;->setSoftApConfiguration(Landroid/net/wifi/SoftApConfiguration;)Z,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#155063)
|
* (since API 30) `Landroid/net/wifi/SoftApInfo;->CHANNEL_WIDTH_*:I,sdk,system-api,test-api`
|
||||||
* (prior to API 30) [`Landroid/net/wifi/WifiManager;->setWifiApConfiguration(Landroid/net/wifi/WifiConfiguration;)Z,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#155067)
|
* (on API 30) `Landroid/net/wifi/SoftApInfo;->CHANNEL_WIDTH_INVALID:I,sdk,system-api,test-api`
|
||||||
* (since API 28) [`Landroid/net/wifi/WifiManager;->unregisterSoftApCallback(Landroid/net/wifi/WifiManager$SoftApCallback;)V,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#155087)
|
* (since API 31) `Landroid/net/wifi/SoftApInfo;->getAutoShutdownTimeoutMillis()J,sdk,system-api,test-api`
|
||||||
* [`Landroid/net/wifi/p2p/WifiP2pGroupList;->getGroupList()Ljava/util/List;,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#158079)
|
* (since API 30) `Landroid/net/wifi/SoftApInfo;->getBandwidth()I,sdk,system-api,test-api`
|
||||||
* [`Landroid/net/wifi/p2p/WifiP2pManager$PersistentGroupInfoListener;->onPersistentGroupInfoAvailable(Landroid/net/wifi/p2p/WifiP2pGroupList;)V,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#158156)
|
* (since API 31) `Landroid/net/wifi/SoftApInfo;->getBssid()Landroid/net/MacAddress;,sdk,system-api,test-api`
|
||||||
* [`Landroid/net/wifi/p2p/WifiP2pManager;->deletePersistentGroup(Landroid/net/wifi/p2p/WifiP2pManager$Channel;ILandroid/net/wifi/p2p/WifiP2pManager$ActionListener;)V,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#158296)
|
* (since API 30) `Landroid/net/wifi/SoftApInfo;->getFrequency()I,sdk,system-api,test-api`
|
||||||
* [`Landroid/net/wifi/p2p/WifiP2pManager;->requestPersistentGroupInfo(Landroid/net/wifi/p2p/WifiP2pManager$Channel;Landroid/net/wifi/p2p/WifiP2pManager$PersistentGroupInfoListener;)V,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#158320)
|
* (since API 31) `Landroid/net/wifi/SoftApInfo;->getWifiStandard()I,sdk,system-api,test-api`
|
||||||
* [`Landroid/net/wifi/p2p/WifiP2pManager;->setWifiP2pChannels(Landroid/net/wifi/p2p/WifiP2pManager$Channel;IILandroid/net/wifi/p2p/WifiP2pManager$ActionListener;)V,system-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#158330)
|
* (since API 30) `Landroid/net/wifi/WifiClient;->getMacAddress()Landroid/net/MacAddress;,sdk,system-api,test-api`
|
||||||
* [`Landroid/provider/Settings$Global;->TETHER_OFFLOAD_DISABLED:Ljava/lang/String;,system-api,test-api,whitelist`](https://android.googlesource.com/platform/prebuilts/runtime/+/4601d91/appcompat/hiddenapi-flags.csv#183757)
|
* (prior to API 30) `Landroid/net/wifi/WifiConfiguration$KeyMgmt;->WPA2_PSK:I,sdk,system-api,test-api`
|
||||||
|
* (since API 30) `Landroid/net/wifi/WifiManager$SoftApCallback;->onBlockedClientConnecting(Landroid/net/wifi/WifiClient;I)V,sdk,system-api,test-api`
|
||||||
|
* (since API 30) `Landroid/net/wifi/WifiManager$SoftApCallback;->onCapabilityChanged(Landroid/net/wifi/SoftApCapability;)V,sdk,system-api,test-api`
|
||||||
|
* (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`
|
||||||
|
* (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`
|
||||||
|
* (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`
|
||||||
|
* (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/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`
|
||||||
|
* `Landroid/net/wifi/p2p/WifiP2pManager;->requestPersistentGroupInfo(Landroid/net/wifi/p2p/WifiP2pManager$Channel;Landroid/net/wifi/p2p/WifiP2pManager$PersistentGroupInfoListener;)V,sdk,system-api,test-api`
|
||||||
|
* `Landroid/net/wifi/p2p/WifiP2pManager;->setWifiP2pChannels(Landroid/net/wifi/p2p/WifiP2pManager$Channel;IILandroid/net/wifi/p2p/WifiP2pManager$ActionListener;)V,sdk,system-api,test-api`
|
||||||
|
* `Landroid/provider/Settings$Global;->TETHER_OFFLOAD_DISABLED:Ljava/lang/String;,sdk,system-api,test-api`
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
@@ -290,12 +330,10 @@ Nonexported system resources:
|
|||||||
* (since API 30) `@com.android.networkstack.tethering:array/config_tether_wifi_p2p_regexs`
|
* (since API 30) `@com.android.networkstack.tethering:array/config_tether_wifi_p2p_regexs`
|
||||||
* (since API 30) `@com.android.networkstack.tethering:array/config_tether_wifi_regexs`
|
* (since API 30) `@com.android.networkstack.tethering:array/config_tether_wifi_regexs`
|
||||||
* (since API 30) `@com.android.networkstack.tethering:array/config_tether_wigig_regexs`
|
* (since API 30) `@com.android.networkstack.tethering:array/config_tether_wigig_regexs`
|
||||||
|
* (since API 30) `@com.android.wifi.resources:bool/config_wifi_p2p_mac_randomization_supported`
|
||||||
* (since API 30) `@com.android.wifi.resources:integer/config_wifiFrameworkSoftApShutDownTimeoutMilliseconds`
|
* (since API 30) `@com.android.wifi.resources:integer/config_wifiFrameworkSoftApShutDownTimeoutMilliseconds`
|
||||||
|
|
||||||
Other:
|
Other: Activity `com.android.settings/.Settings$TetherSettingsActivity` is assumed to be exported.
|
||||||
|
|
||||||
* (since API 29) `android.net.wifi.p2p.WifiP2pConfig` needs to be parcelized in a very specific order, except for possible extra fields at the end. (used only for safe mode)
|
|
||||||
* 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;
|
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).
|
(prior to API 24) `RULE_PRIORITY_DEFAULT_NETWORK` is assumed to be 22000 (or at least > 18000).
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ buildscript {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath(kotlin("gradle-plugin", "1.5.20"))
|
classpath(kotlin("gradle-plugin", "1.5.21"))
|
||||||
classpath("com.android.tools.build:gradle:7.0.0-beta05")
|
classpath("com.android.tools.build:gradle:7.0.0")
|
||||||
classpath("com.google.firebase:firebase-crashlytics-gradle:2.7.1")
|
classpath("com.google.firebase:firebase-crashlytics-gradle:2.7.1")
|
||||||
classpath("com.google.android.gms:oss-licenses-plugin:0.10.4")
|
classpath("com.google.android.gms:oss-licenses-plugin:0.10.4")
|
||||||
classpath("com.google.gms:google-services:4.3.8")
|
classpath("com.google.gms:google-services:4.3.8")
|
||||||
|
|||||||
@@ -9,23 +9,23 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
val javaVersion = JavaVersion.VERSION_1_8
|
val javaVersion = JavaVersion.VERSION_11
|
||||||
val targetSdk = 29
|
val targetSdk = 29
|
||||||
buildToolsVersion = "30.0.3"
|
buildToolsVersion = "31.0.0"
|
||||||
compileOptions {
|
compileOptions {
|
||||||
isCoreLibraryDesugaringEnabled = true
|
isCoreLibraryDesugaringEnabled = true
|
||||||
sourceCompatibility = javaVersion
|
sourceCompatibility = javaVersion
|
||||||
targetCompatibility = javaVersion
|
targetCompatibility = javaVersion
|
||||||
}
|
}
|
||||||
compileSdk = 30
|
compileSdk = 31
|
||||||
kotlinOptions.jvmTarget = javaVersion.toString()
|
kotlinOptions.jvmTarget = javaVersion.toString()
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "be.mygod.vpnhotspot"
|
applicationId = "be.mygod.vpnhotspot"
|
||||||
minSdk = 21
|
minSdk = 21
|
||||||
this.targetSdk = targetSdk
|
this.targetSdk = targetSdk
|
||||||
resourceConfigurations.addAll(arrayOf("it", "ru", "zh-rCN", "zh-rTW"))
|
resourceConfigurations.addAll(arrayOf("it", "ru", "zh-rCN", "zh-rTW"))
|
||||||
versionCode = 262
|
versionCode = 277
|
||||||
versionName = "2.11.9"
|
versionName = "2.12.6"
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
javaCompileOptions.annotationProcessorOptions.arguments.apply {
|
javaCompileOptions.annotationProcessorOptions.arguments.apply {
|
||||||
put("room.expandProjection", "true")
|
put("room.expandProjection", "true")
|
||||||
@@ -70,11 +70,11 @@ dependencies {
|
|||||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.5")
|
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.5")
|
||||||
kapt("androidx.room:room-compiler:$roomVersion")
|
kapt("androidx.room:room-compiler:$roomVersion")
|
||||||
implementation(kotlin("stdlib-jdk8"))
|
implementation(kotlin("stdlib-jdk8"))
|
||||||
implementation("androidx.appcompat:appcompat:1.3.0") // https://issuetracker.google.com/issues/151603528
|
implementation("androidx.appcompat:appcompat:1.3.1") // https://issuetracker.google.com/issues/151603528
|
||||||
implementation("androidx.browser:browser:1.3.0")
|
implementation("androidx.browser:browser:1.3.0")
|
||||||
implementation("androidx.core:core-ktx:1.6.0")
|
implementation("androidx.core:core-ktx:1.6.0")
|
||||||
implementation("androidx.emoji:emoji:1.1.0")
|
implementation("androidx.emoji:emoji:1.1.0")
|
||||||
implementation("androidx.fragment:fragment-ktx:1.3.5")
|
implementation("androidx.fragment:fragment-ktx:1.3.6")
|
||||||
implementation("androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion")
|
implementation("androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion")
|
||||||
implementation("androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion")
|
implementation("androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion")
|
||||||
implementation("androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion")
|
implementation("androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion")
|
||||||
@@ -83,9 +83,9 @@ dependencies {
|
|||||||
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
|
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
|
||||||
implementation("com.android.billingclient:billing-ktx:4.0.0")
|
implementation("com.android.billingclient:billing-ktx:4.0.0")
|
||||||
implementation("com.google.android.gms:play-services-oss-licenses:17.0.0")
|
implementation("com.google.android.gms:play-services-oss-licenses:17.0.0")
|
||||||
implementation("com.google.android.material:material:1.4.0")
|
implementation("com.google.android.material:material:1.5.0-alpha01")
|
||||||
implementation("com.google.firebase:firebase-analytics-ktx:19.0.0")
|
implementation("com.google.firebase:firebase-analytics-ktx:19.0.0")
|
||||||
implementation("com.google.firebase:firebase-crashlytics:18.1.0")
|
implementation("com.google.firebase:firebase-crashlytics:18.2.0")
|
||||||
implementation("com.google.zxing:core:3.4.1")
|
implementation("com.google.zxing:core:3.4.1")
|
||||||
implementation("com.jakewharton.timber:timber:4.7.1")
|
implementation("com.jakewharton.timber:timber:4.7.1")
|
||||||
implementation("com.linkedin.dexmaker:dexmaker:2.28.1")
|
implementation("com.linkedin.dexmaker:dexmaker:2.28.1")
|
||||||
|
|||||||
@@ -9,9 +9,6 @@
|
|||||||
<uses-feature
|
<uses-feature
|
||||||
android:name="android.hardware.ethernet"
|
android:name="android.hardware.ethernet"
|
||||||
android:required="false"/>
|
android:required="false"/>
|
||||||
<uses-feature
|
|
||||||
android:name="android.software.leanback"
|
|
||||||
android:required="false"/>
|
|
||||||
<uses-feature
|
<uses-feature
|
||||||
android:name="android.hardware.touchscreen"
|
android:name="android.hardware.touchscreen"
|
||||||
android:required="false"/>
|
android:required="false"/>
|
||||||
@@ -24,6 +21,9 @@
|
|||||||
<uses-feature
|
<uses-feature
|
||||||
android:name="android.hardware.wifi.direct"
|
android:name="android.hardware.wifi.direct"
|
||||||
android:required="false"/>
|
android:required="false"/>
|
||||||
|
<uses-feature
|
||||||
|
android:name="android.software.leanback"
|
||||||
|
android:required="false"/>
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
|
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
|
||||||
@@ -52,12 +52,15 @@
|
|||||||
<uses-permission-sdk-23 android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
<uses-permission-sdk-23 android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
||||||
<!-- Required since API 29 -->
|
<!-- Required since API 29 -->
|
||||||
<uses-permission-sdk-23 android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
<uses-permission-sdk-23 android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
||||||
<!-- Required since API 31 -->
|
<!-- Required since API 31, when targeting API 31 -->
|
||||||
|
<!--
|
||||||
<uses-permission-sdk-23 android:name="android.permission.BLUETOOTH_CONNECT"/>
|
<uses-permission-sdk-23 android:name="android.permission.BLUETOOTH_CONNECT"/>
|
||||||
|
-->
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".App"
|
android:name=".App"
|
||||||
android:allowBackup="false"
|
android:allowBackup="false"
|
||||||
|
android:dataExtractionRules="@xml/no_backup"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:banner="@mipmap/banner"
|
android:banner="@mipmap/banner"
|
||||||
android:hasFragileUserData="true"
|
android:hasFragileUserData="true"
|
||||||
@@ -69,6 +72,7 @@
|
|||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
|
android:exported="true"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
android:windowSoftInputMode="stateAlwaysHidden">
|
android:windowSoftInputMode="stateAlwaysHidden">
|
||||||
@@ -100,6 +104,7 @@
|
|||||||
<service
|
<service
|
||||||
android:name=".manage.RepeaterTileService"
|
android:name=".manage.RepeaterTileService"
|
||||||
android:directBootAware="true"
|
android:directBootAware="true"
|
||||||
|
android:exported="true"
|
||||||
android:icon="@drawable/ic_action_settings_input_antenna"
|
android:icon="@drawable/ic_action_settings_input_antenna"
|
||||||
android:label="@string/title_repeater"
|
android:label="@string/title_repeater"
|
||||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
|
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
|
||||||
@@ -115,6 +120,7 @@
|
|||||||
android:name=".manage.LocalOnlyHotspotTileService"
|
android:name=".manage.LocalOnlyHotspotTileService"
|
||||||
android:directBootAware="true"
|
android:directBootAware="true"
|
||||||
android:enabled="@bool/api_ge_26"
|
android:enabled="@bool/api_ge_26"
|
||||||
|
android:exported="true"
|
||||||
android:icon="@drawable/ic_action_perm_scan_wifi"
|
android:icon="@drawable/ic_action_perm_scan_wifi"
|
||||||
android:label="@string/tethering_temp_hotspot"
|
android:label="@string/tethering_temp_hotspot"
|
||||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
|
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
|
||||||
@@ -129,6 +135,7 @@
|
|||||||
<service
|
<service
|
||||||
android:name=".manage.TetheringTileService$Wifi"
|
android:name=".manage.TetheringTileService$Wifi"
|
||||||
android:directBootAware="true"
|
android:directBootAware="true"
|
||||||
|
android:exported="true"
|
||||||
android:icon="@drawable/ic_device_wifi_tethering"
|
android:icon="@drawable/ic_device_wifi_tethering"
|
||||||
android:label="@string/tethering_manage_wifi"
|
android:label="@string/tethering_manage_wifi"
|
||||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
|
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
|
||||||
@@ -136,10 +143,14 @@
|
|||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.service.quicksettings.action.QS_TILE" />
|
<action android:name="android.service.quicksettings.action.QS_TILE" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
<meta-data
|
||||||
|
android:name="android.service.quicksettings.TOGGLEABLE_TILE"
|
||||||
|
android:value="true" />
|
||||||
</service>
|
</service>
|
||||||
<service
|
<service
|
||||||
android:name=".manage.TetheringTileService$Usb"
|
android:name=".manage.TetheringTileService$Usb"
|
||||||
android:directBootAware="true"
|
android:directBootAware="true"
|
||||||
|
android:exported="true"
|
||||||
android:icon="@drawable/ic_device_usb"
|
android:icon="@drawable/ic_device_usb"
|
||||||
android:label="@string/tethering_manage_usb"
|
android:label="@string/tethering_manage_usb"
|
||||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
|
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
|
||||||
@@ -147,10 +158,14 @@
|
|||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.service.quicksettings.action.QS_TILE" />
|
<action android:name="android.service.quicksettings.action.QS_TILE" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
<meta-data
|
||||||
|
android:name="android.service.quicksettings.TOGGLEABLE_TILE"
|
||||||
|
android:value="true" />
|
||||||
</service>
|
</service>
|
||||||
<service
|
<service
|
||||||
android:name=".manage.TetheringTileService$Bluetooth"
|
android:name=".manage.TetheringTileService$Bluetooth"
|
||||||
android:directBootAware="true"
|
android:directBootAware="true"
|
||||||
|
android:exported="true"
|
||||||
android:icon="@drawable/ic_device_bluetooth"
|
android:icon="@drawable/ic_device_bluetooth"
|
||||||
android:label="@string/tethering_manage_bluetooth"
|
android:label="@string/tethering_manage_bluetooth"
|
||||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
|
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
|
||||||
@@ -158,11 +173,15 @@
|
|||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.service.quicksettings.action.QS_TILE" />
|
<action android:name="android.service.quicksettings.action.QS_TILE" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
<meta-data
|
||||||
|
android:name="android.service.quicksettings.TOGGLEABLE_TILE"
|
||||||
|
android:value="true" />
|
||||||
</service>
|
</service>
|
||||||
<service
|
<service
|
||||||
android:name=".manage.TetheringTileService$Ethernet"
|
android:name=".manage.TetheringTileService$Ethernet"
|
||||||
android:directBootAware="true"
|
android:directBootAware="true"
|
||||||
android:enabled="@bool/api_ge_30"
|
android:enabled="@bool/api_ge_30"
|
||||||
|
android:exported="true"
|
||||||
android:icon="@drawable/ic_content_inbox"
|
android:icon="@drawable/ic_content_inbox"
|
||||||
android:label="@string/tethering_manage_ethernet"
|
android:label="@string/tethering_manage_ethernet"
|
||||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
|
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
|
||||||
@@ -170,11 +189,15 @@
|
|||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.service.quicksettings.action.QS_TILE" />
|
<action android:name="android.service.quicksettings.action.QS_TILE" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
<meta-data
|
||||||
|
android:name="android.service.quicksettings.TOGGLEABLE_TILE"
|
||||||
|
android:value="true" />
|
||||||
</service>
|
</service>
|
||||||
<service
|
<service
|
||||||
android:name=".manage.TetheringTileService$Ncm"
|
android:name=".manage.TetheringTileService$Ncm"
|
||||||
android:directBootAware="true"
|
android:directBootAware="true"
|
||||||
android:enabled="@bool/api_ge_30"
|
android:enabled="@bool/api_ge_30"
|
||||||
|
android:exported="true"
|
||||||
android:icon="@drawable/ic_action_settings_ethernet"
|
android:icon="@drawable/ic_action_settings_ethernet"
|
||||||
android:label="@string/tethering_manage_ncm"
|
android:label="@string/tethering_manage_ncm"
|
||||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
|
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
|
||||||
@@ -182,11 +205,15 @@
|
|||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.service.quicksettings.action.QS_TILE" />
|
<action android:name="android.service.quicksettings.action.QS_TILE" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
<meta-data
|
||||||
|
android:name="android.service.quicksettings.TOGGLEABLE_TILE"
|
||||||
|
android:value="true" />
|
||||||
</service>
|
</service>
|
||||||
<service
|
<service
|
||||||
android:name=".manage.TetheringTileService$WiGig"
|
android:name=".manage.TetheringTileService$WiGig"
|
||||||
android:directBootAware="true"
|
android:directBootAware="true"
|
||||||
android:enabled="@bool/api_ge_30"
|
android:enabled="@bool/api_ge_30"
|
||||||
|
android:exported="true"
|
||||||
android:icon="@drawable/ic_image_flash_on"
|
android:icon="@drawable/ic_image_flash_on"
|
||||||
android:label="@string/tethering_manage_wigig"
|
android:label="@string/tethering_manage_wigig"
|
||||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
|
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
|
||||||
@@ -194,12 +221,16 @@
|
|||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.service.quicksettings.action.QS_TILE" />
|
<action android:name="android.service.quicksettings.action.QS_TILE" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
<meta-data
|
||||||
|
android:name="android.service.quicksettings.TOGGLEABLE_TILE"
|
||||||
|
android:value="true" />
|
||||||
</service>
|
</service>
|
||||||
<!--suppress DeprecatedClassUsageInspection -->
|
<!--suppress DeprecatedClassUsageInspection -->
|
||||||
<service
|
<service
|
||||||
android:name=".manage.TetheringTileService$WifiLegacy"
|
android:name=".manage.TetheringTileService$WifiLegacy"
|
||||||
android:directBootAware="true"
|
android:directBootAware="true"
|
||||||
android:enabled="@bool/api_lt_25"
|
android:enabled="@bool/api_lt_25"
|
||||||
|
android:exported="true"
|
||||||
android:icon="@drawable/ic_device_wifi_tethering"
|
android:icon="@drawable/ic_device_wifi_tethering"
|
||||||
android:label="@string/tethering_manage_wifi_legacy"
|
android:label="@string/tethering_manage_wifi_legacy"
|
||||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
|
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
|
||||||
@@ -207,12 +238,16 @@
|
|||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.service.quicksettings.action.QS_TILE" />
|
<action android:name="android.service.quicksettings.action.QS_TILE" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
<meta-data
|
||||||
|
android:name="android.service.quicksettings.TOGGLEABLE_TILE"
|
||||||
|
android:value="true" />
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".BootReceiver"
|
android:name=".BootReceiver"
|
||||||
android:directBootAware="true"
|
android:directBootAware="true"
|
||||||
android:enabled="false">
|
android:enabled="false"
|
||||||
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.BOOT_COMPLETED"/>
|
<action android:name="android.intent.action.BOOT_COMPLETED"/>
|
||||||
<action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
|
<action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import android.os.RemoteException
|
|||||||
import android.system.Os
|
import android.system.Os
|
||||||
import android.system.OsConstants
|
import android.system.OsConstants
|
||||||
import androidx.collection.LongSparseArray
|
import androidx.collection.LongSparseArray
|
||||||
import androidx.collection.set
|
|
||||||
import androidx.collection.valueIterator
|
import androidx.collection.valueIterator
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.channels.*
|
import kotlinx.coroutines.channels.*
|
||||||
@@ -102,6 +101,7 @@ class RootServer {
|
|||||||
|
|
||||||
private fun readUnexpectedStderr(): String? {
|
private fun readUnexpectedStderr(): String? {
|
||||||
if (!this::process.isInitialized) return null
|
if (!this::process.isInitialized) return null
|
||||||
|
Logger.me.d("Attempting to read stderr")
|
||||||
var available = process.errorStream.available()
|
var available = process.errorStream.available()
|
||||||
return if (available <= 0) null else String(ByteArrayOutputStream().apply {
|
return if (available <= 0) null else String(ByteArrayOutputStream().apply {
|
||||||
try {
|
try {
|
||||||
@@ -146,7 +146,9 @@ class RootServer {
|
|||||||
try {
|
try {
|
||||||
val token2 = UUID.randomUUID().toString()
|
val token2 = UUID.randomUUID().toString()
|
||||||
val persistence = File(context.codeCacheDir, ".librootkotlinx-uuid")
|
val persistence = File(context.codeCacheDir, ".librootkotlinx-uuid")
|
||||||
val uuid = context.packageName + '@' + if (persistence.canRead()) persistence.readText() else {
|
val uuid = context.packageName + '@' + try {
|
||||||
|
persistence.readText()
|
||||||
|
} catch (_: FileNotFoundException) {
|
||||||
UUID.randomUUID().toString().also { persistence.writeText(it) }
|
UUID.randomUUID().toString().also { persistence.writeText(it) }
|
||||||
}
|
}
|
||||||
val (script, relocated) = AppProcess.relocateScript(uuid)
|
val (script, relocated) = AppProcess.relocateScript(uuid)
|
||||||
@@ -218,9 +220,9 @@ class RootServer {
|
|||||||
throw e
|
throw e
|
||||||
} finally {
|
} finally {
|
||||||
Logger.me.d("Waiting for exit")
|
Logger.me.d("Waiting for exit")
|
||||||
errorReader.await()
|
withContext(NonCancellable) { errorReader.await() }
|
||||||
process.waitFor()
|
process.waitFor()
|
||||||
withContext(NonCancellable) { closeInternal(true) }
|
closeInternal(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -246,7 +248,7 @@ class RootServer {
|
|||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
val callback = Callback.Ordinary(this, counter, classLoader, future as CompletableDeferred<Parcelable?>)
|
val callback = Callback.Ordinary(this, counter, classLoader, future as CompletableDeferred<Parcelable?>)
|
||||||
if (active) {
|
if (active) {
|
||||||
callbackLookup[counter] = callback
|
callbackLookup.append(counter, callback)
|
||||||
sendLocked(command)
|
sendLocked(command)
|
||||||
} else future.cancel()
|
} else future.cancel()
|
||||||
callback
|
callback
|
||||||
@@ -277,7 +279,7 @@ class RootServer {
|
|||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
val callback = Callback.Channel(this@RootServer, counter, classLoader, this as SendChannel<Parcelable?>)
|
val callback = Callback.Channel(this@RootServer, counter, classLoader, this as SendChannel<Parcelable?>)
|
||||||
if (active) {
|
if (active) {
|
||||||
callbackLookup[counter] = callback
|
callbackLookup.append(counter, callback)
|
||||||
sendLocked(command)
|
sendLocked(command)
|
||||||
} else callback.finish.cancel()
|
} else callback.finish.cancel()
|
||||||
callback
|
callback
|
||||||
@@ -290,7 +292,7 @@ class RootServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun closeInternal(fromWorker: Boolean = false) = synchronized(callbackLookup) {
|
private fun closeInternal(fromWorker: Boolean = false) = synchronized(callbackLookup) {
|
||||||
if (active) {
|
if (active) {
|
||||||
active = false
|
active = false
|
||||||
Logger.me.d(if (fromWorker) "Shutting down from worker" else "Shutting down from client")
|
Logger.me.d(if (fromWorker) "Shutting down from worker" else "Shutting down from client")
|
||||||
@@ -432,7 +434,7 @@ class RootServer {
|
|||||||
}
|
}
|
||||||
is RootCommand<*> -> {
|
is RootCommand<*> -> {
|
||||||
val commandJob = Job()
|
val commandJob = Job()
|
||||||
cancellables[callback] = { commandJob.cancel() }
|
cancellables.append(callback) { commandJob.cancel() }
|
||||||
defaultWorker.launch(commandJob) {
|
defaultWorker.launch(commandJob) {
|
||||||
val result = try {
|
val result = try {
|
||||||
val result = command.execute();
|
val result = command.execute();
|
||||||
@@ -450,7 +452,7 @@ class RootServer {
|
|||||||
val result = try {
|
val result = try {
|
||||||
coroutineScope {
|
coroutineScope {
|
||||||
command.create(this).also {
|
command.create(this).also {
|
||||||
cancellables[callback] = { it.cancel() }
|
cancellables.append(callback) { it.cancel() }
|
||||||
}.consumeEach { result ->
|
}.consumeEach { result ->
|
||||||
withContext(callbackWorker) { output.pushResult(callback, result) }
|
withContext(callbackWorker) { output.pushResult(callback, result) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import androidx.core.os.bundleOf
|
|||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.setFragmentResult
|
import androidx.fragment.app.setFragmentResult
|
||||||
import androidx.fragment.app.setFragmentResultListener
|
import androidx.fragment.app.setFragmentResultListener
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -44,7 +45,7 @@ abstract class AlertDialogFragment<Arg : Parcelable, Ret : Parcelable> :
|
|||||||
fun key(resultKey: String = javaClass.name) = args().putString(KEY_RESULT, resultKey)
|
fun key(resultKey: String = javaClass.name) = args().putString(KEY_RESULT, resultKey)
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): AlertDialog =
|
override fun onCreateDialog(savedInstanceState: Bundle?): AlertDialog =
|
||||||
AlertDialog.Builder(requireContext()).also { it.prepare(this) }.create()
|
MaterialAlertDialogBuilder(requireContext()).also { it.prepare(this) }.create()
|
||||||
|
|
||||||
override fun onClick(dialog: DialogInterface?, which: Int) {
|
override fun onClick(dialog: DialogInterface?, which: Int) {
|
||||||
setFragmentResult(resultKey ?: return, Bundle().apply {
|
setFragmentResult(resultKey ?: return, Bundle().apply {
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ class App : Application() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Timber.plant(object : Timber.DebugTree() {
|
Timber.plant(object : Timber.DebugTree() {
|
||||||
|
@SuppressLint("LogNotTimber")
|
||||||
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
|
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
|
||||||
if (t == null) {
|
if (t == null) {
|
||||||
if (priority != Log.DEBUG || BuildConfig.DEBUG) Log.println(priority, tag, message)
|
if (priority != Log.DEBUG || BuildConfig.DEBUG) Log.println(priority, tag, message)
|
||||||
|
|||||||
@@ -6,16 +6,13 @@ import android.net.wifi.WifiManager
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import be.mygod.vpnhotspot.net.IpNeighbour
|
import be.mygod.vpnhotspot.net.IpNeighbour
|
||||||
import be.mygod.vpnhotspot.net.TetherType
|
|
||||||
import be.mygod.vpnhotspot.net.TetheringManager
|
|
||||||
import be.mygod.vpnhotspot.net.TetheringManager.localOnlyTetheredIfaces
|
|
||||||
import be.mygod.vpnhotspot.net.monitor.IpNeighbourMonitor
|
import be.mygod.vpnhotspot.net.monitor.IpNeighbourMonitor
|
||||||
import be.mygod.vpnhotspot.net.monitor.TetherTimeoutMonitor
|
import be.mygod.vpnhotspot.net.monitor.TetherTimeoutMonitor
|
||||||
import be.mygod.vpnhotspot.net.wifi.SoftApConfigurationCompat.Companion.toCompat
|
import be.mygod.vpnhotspot.net.wifi.SoftApConfigurationCompat.Companion.toCompat
|
||||||
import be.mygod.vpnhotspot.net.wifi.WifiApManager
|
import be.mygod.vpnhotspot.net.wifi.WifiApManager
|
||||||
|
import be.mygod.vpnhotspot.net.wifi.WifiApManager.wifiApState
|
||||||
import be.mygod.vpnhotspot.util.Services
|
import be.mygod.vpnhotspot.util.Services
|
||||||
import be.mygod.vpnhotspot.util.StickyEvent1
|
import be.mygod.vpnhotspot.util.StickyEvent1
|
||||||
import be.mygod.vpnhotspot.util.broadcastReceiver
|
|
||||||
import be.mygod.vpnhotspot.widget.SmartSnackbar
|
import be.mygod.vpnhotspot.widget.SmartSnackbar
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
@@ -43,7 +40,8 @@ class LocalOnlyHotspotService : IpNeighbourMonitoringService(), CoroutineScope {
|
|||||||
null -> return // stopped
|
null -> return // stopped
|
||||||
"" -> WifiApManager.cancelLocalOnlyHotspotRequest()
|
"" -> WifiApManager.cancelLocalOnlyHotspotRequest()
|
||||||
}
|
}
|
||||||
reservation?.close() ?: stopService()
|
reservation?.close()
|
||||||
|
stopService()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,24 +54,6 @@ class LocalOnlyHotspotService : IpNeighbourMonitoringService(), CoroutineScope {
|
|||||||
override val coroutineContext = dispatcher + Job()
|
override val coroutineContext = dispatcher + Job()
|
||||||
private var routingManager: RoutingManager? = null
|
private var routingManager: RoutingManager? = null
|
||||||
private var timeoutMonitor: TetherTimeoutMonitor? = null
|
private var timeoutMonitor: TetherTimeoutMonitor? = null
|
||||||
private var receiverRegistered = false
|
|
||||||
private val receiver = broadcastReceiver { _, intent ->
|
|
||||||
val ifaces = (intent.localOnlyTetheredIfaces ?: return@broadcastReceiver).filter {
|
|
||||||
TetherType.ofInterface(it) != TetherType.WIFI_P2P
|
|
||||||
}
|
|
||||||
Timber.d("onTetherStateChangedLocked: $ifaces")
|
|
||||||
check(ifaces.size <= 1)
|
|
||||||
val iface = ifaces.singleOrNull()
|
|
||||||
binder.iface = iface
|
|
||||||
if (iface.isNullOrEmpty()) stopService() else launch {
|
|
||||||
val routingManager = routingManager
|
|
||||||
if (routingManager == null) {
|
|
||||||
this@LocalOnlyHotspotService.routingManager = RoutingManager.LocalOnly(this@LocalOnlyHotspotService,
|
|
||||||
iface).apply { start() }
|
|
||||||
IpNeighbourMonitor.registerCallback(this@LocalOnlyHotspotService)
|
|
||||||
} else check(iface == routingManager.downstream)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
override val activeIfaces get() = binder.iface.let { if (it.isNullOrEmpty()) emptyList() else listOf(it) }
|
override val activeIfaces get() = binder.iface.let { if (it.isNullOrEmpty()) emptyList() else listOf(it) }
|
||||||
|
|
||||||
override fun onBind(intent: Intent?) = binder
|
override fun onBind(intent: Intent?) = binder
|
||||||
@@ -87,20 +67,36 @@ class LocalOnlyHotspotService : IpNeighbourMonitoringService(), CoroutineScope {
|
|||||||
override fun onStarted(reservation: WifiManager.LocalOnlyHotspotReservation?) {
|
override fun onStarted(reservation: WifiManager.LocalOnlyHotspotReservation?) {
|
||||||
if (reservation == null) onFailed(-2) else {
|
if (reservation == null) onFailed(-2) else {
|
||||||
this@LocalOnlyHotspotService.reservation = reservation
|
this@LocalOnlyHotspotService.reservation = reservation
|
||||||
if (!receiverRegistered) {
|
val configuration = binder.configuration!!
|
||||||
val configuration = binder.configuration!!
|
if (Build.VERSION.SDK_INT < 30 && configuration.isAutoShutdownEnabled) {
|
||||||
if (Build.VERSION.SDK_INT < 30 && configuration.isAutoShutdownEnabled) {
|
timeoutMonitor = TetherTimeoutMonitor(configuration.shutdownTimeoutMillis,
|
||||||
timeoutMonitor = TetherTimeoutMonitor(configuration.shutdownTimeoutMillis,
|
coroutineContext) { reservation.close() }
|
||||||
coroutineContext) { reservation.close() }
|
}
|
||||||
|
// based on: https://android.googlesource.com/platform/packages/services/Car/+/df5cd06/service/src/com/android/car/CarProjectionService.java#160
|
||||||
|
val sticky = registerReceiver(null, IntentFilter(WifiApManager.WIFI_AP_STATE_CHANGED_ACTION))!!
|
||||||
|
val apState = sticky.wifiApState
|
||||||
|
val iface = sticky.getStringExtra(WifiApManager.EXTRA_WIFI_AP_INTERFACE_NAME)
|
||||||
|
if (apState != WifiApManager.WIFI_AP_STATE_ENABLED || iface.isNullOrEmpty()) {
|
||||||
|
if (apState == WifiApManager.WIFI_AP_STATE_FAILED) {
|
||||||
|
SmartSnackbar.make(getString(R.string.tethering_temp_hotspot_failure,
|
||||||
|
WifiApManager.failureReasonLookup(sticky.getIntExtra(
|
||||||
|
WifiApManager.EXTRA_WIFI_AP_FAILURE_REASON, 0)))).show()
|
||||||
}
|
}
|
||||||
registerReceiver(receiver, IntentFilter(TetheringManager.ACTION_TETHER_STATE_CHANGED))
|
return stopService()
|
||||||
receiverRegistered = true
|
}
|
||||||
|
binder.iface = iface
|
||||||
|
launch {
|
||||||
|
check(routingManager == null)
|
||||||
|
routingManager = RoutingManager.LocalOnly(
|
||||||
|
this@LocalOnlyHotspotService, iface).apply { start() }
|
||||||
|
IpNeighbourMonitor.registerCallback(this@LocalOnlyHotspotService)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStopped() {
|
override fun onStopped() {
|
||||||
Timber.d("LOHCallback.onStopped")
|
Timber.d("LOHCallback.onStopped")
|
||||||
|
reservation?.close()
|
||||||
reservation = null
|
reservation = null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,14 +148,10 @@ class LocalOnlyHotspotService : IpNeighbourMonitoringService(), CoroutineScope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun unregisterReceiver(exit: Boolean = false) {
|
private fun unregisterReceiver(exit: Boolean = false) {
|
||||||
if (receiverRegistered) {
|
IpNeighbourMonitor.unregisterCallback(this)
|
||||||
unregisterReceiver(receiver)
|
if (Build.VERSION.SDK_INT >= 28) {
|
||||||
IpNeighbourMonitor.unregisterCallback(this)
|
timeoutMonitor?.close()
|
||||||
if (Build.VERSION.SDK_INT >= 28) {
|
timeoutMonitor = null
|
||||||
timeoutMonitor?.close()
|
|
||||||
timeoutMonitor = null
|
|
||||||
}
|
|
||||||
receiverRegistered = false
|
|
||||||
}
|
}
|
||||||
launch {
|
launch {
|
||||||
routingManager?.stop()
|
routingManager?.stop()
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import android.provider.Settings
|
|||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import be.mygod.librootkotlinx.useParcel
|
|
||||||
import be.mygod.vpnhotspot.App.Companion.app
|
import be.mygod.vpnhotspot.App.Companion.app
|
||||||
import be.mygod.vpnhotspot.net.MacAddressCompat
|
import be.mygod.vpnhotspot.net.MacAddressCompat
|
||||||
import be.mygod.vpnhotspot.net.monitor.TetherTimeoutMonitor
|
import be.mygod.vpnhotspot.net.monitor.TetherTimeoutMonitor
|
||||||
@@ -53,14 +52,10 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene
|
|||||||
private const val KEY_AUTO_SHUTDOWN = "service.repeater.autoShutdown"
|
private const val KEY_AUTO_SHUTDOWN = "service.repeater.autoShutdown"
|
||||||
private const val KEY_SHUTDOWN_TIMEOUT = "service.repeater.shutdownTimeout"
|
private const val KEY_SHUTDOWN_TIMEOUT = "service.repeater.shutdownTimeout"
|
||||||
private const val KEY_DEVICE_ADDRESS = "service.repeater.mac"
|
private const val KEY_DEVICE_ADDRESS = "service.repeater.mac"
|
||||||
/**
|
|
||||||
* Placeholder for bypassing networkName check.
|
|
||||||
*/
|
|
||||||
private const val PLACEHOLDER_NETWORK_NAME = "DIRECT-00-VPNHotspot"
|
|
||||||
|
|
||||||
var persistentSupported = false
|
var persistentSupported = false
|
||||||
|
|
||||||
@delegate:TargetApi(29)
|
@get:RequiresApi(29)
|
||||||
private val hasP2pValidateName by lazy {
|
private val hasP2pValidateName by lazy {
|
||||||
val array = Build.VERSION.SECURITY_PATCH.split('-', limit = 3)
|
val array = Build.VERSION.SECURITY_PATCH.split('-', limit = 3)
|
||||||
val y = array.getOrNull(0)?.toIntOrNull()
|
val y = array.getOrNull(0)?.toIntOrNull()
|
||||||
@@ -70,6 +65,7 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene
|
|||||||
val safeModeConfigurable get() = Build.VERSION.SDK_INT >= 29 && hasP2pValidateName
|
val safeModeConfigurable get() = Build.VERSION.SDK_INT >= 29 && hasP2pValidateName
|
||||||
val safeMode get() = Build.VERSION.SDK_INT >= 29 &&
|
val safeMode get() = Build.VERSION.SDK_INT >= 29 &&
|
||||||
(!hasP2pValidateName || app.pref.getBoolean(KEY_SAFE_MODE, true))
|
(!hasP2pValidateName || app.pref.getBoolean(KEY_SAFE_MODE, true))
|
||||||
|
private val mNetworkName by lazy { UnblockCentral.WifiP2pConfig_Builder_mNetworkName }
|
||||||
|
|
||||||
var networkName: String?
|
var networkName: String?
|
||||||
get() = app.pref.getString(KEY_NETWORK_NAME, null)
|
get() = app.pref.getString(KEY_NETWORK_NAME, null)
|
||||||
@@ -79,7 +75,7 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene
|
|||||||
set(value) = app.pref.edit { putString(KEY_PASSPHRASE, value) }
|
set(value) = app.pref.edit { putString(KEY_PASSPHRASE, value) }
|
||||||
var operatingBand: Int
|
var operatingBand: Int
|
||||||
@SuppressLint("InlinedApi")
|
@SuppressLint("InlinedApi")
|
||||||
get() = app.pref.getInt(KEY_OPERATING_BAND, SoftApConfigurationCompat.BAND_ANY)
|
get() = app.pref.getInt(KEY_OPERATING_BAND, SoftApConfigurationCompat.BAND_LEGACY)
|
||||||
set(value) = app.pref.edit { putInt(KEY_OPERATING_BAND, value) }
|
set(value) = app.pref.edit { putInt(KEY_OPERATING_BAND, value) }
|
||||||
var operatingChannel: Int
|
var operatingChannel: Int
|
||||||
get() {
|
get() {
|
||||||
@@ -398,59 +394,39 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene
|
|||||||
}
|
}
|
||||||
val networkName = networkName
|
val networkName = networkName
|
||||||
val passphrase = passphrase
|
val passphrase = passphrase
|
||||||
try {
|
@SuppressLint("MissingPermission") // missing permission will simply leading to returning ERROR
|
||||||
if (!safeMode || networkName == null || passphrase == null) {
|
if (!safeMode || networkName == null || passphrase == null) {
|
||||||
persistNextGroup = true
|
persistNextGroup = true
|
||||||
p2pManager.createGroup(channel, listener)
|
p2pManager.createGroup(channel, listener)
|
||||||
} else @TargetApi(29) {
|
} else @TargetApi(29) {
|
||||||
p2pManager.createGroup(channel, WifiP2pConfig.Builder().apply {
|
p2pManager.createGroup(channel, WifiP2pConfig.Builder().apply {
|
||||||
setNetworkName(PLACEHOLDER_NETWORK_NAME)
|
try {
|
||||||
setPassphrase(passphrase)
|
mNetworkName.set(this, networkName) // bypass networkName check
|
||||||
when (val oc = operatingChannel) {
|
} catch (e: ReflectiveOperationException) {
|
||||||
0 -> setGroupOperatingBand(when (val band = operatingBand) {
|
Timber.w(e)
|
||||||
SoftApConfigurationCompat.BAND_ANY -> WifiP2pConfig.GROUP_OWNER_BAND_AUTO
|
try {
|
||||||
SoftApConfigurationCompat.BAND_2GHZ -> WifiP2pConfig.GROUP_OWNER_BAND_2GHZ
|
setNetworkName(networkName)
|
||||||
SoftApConfigurationCompat.BAND_5GHZ -> WifiP2pConfig.GROUP_OWNER_BAND_5GHZ
|
} catch (e: IllegalArgumentException) {
|
||||||
else -> throw IllegalArgumentException("Unknown band $band")
|
Timber.w(e)
|
||||||
})
|
return startFailure(e.readableMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setPassphrase(passphrase)
|
||||||
|
when (val oc = operatingChannel) {
|
||||||
|
0 -> setGroupOperatingBand(when (val band = operatingBand) {
|
||||||
|
SoftApConfigurationCompat.BAND_2GHZ -> WifiP2pConfig.GROUP_OWNER_BAND_2GHZ
|
||||||
|
SoftApConfigurationCompat.BAND_5GHZ -> WifiP2pConfig.GROUP_OWNER_BAND_5GHZ
|
||||||
else -> {
|
else -> {
|
||||||
setGroupOperatingFrequency(SoftApConfigurationCompat.channelToFrequency(operatingBand, oc))
|
require(SoftApConfigurationCompat.isLegacyEitherBand(band)) { "Unknown band $band" }
|
||||||
|
WifiP2pConfig.GROUP_OWNER_BAND_AUTO
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
else -> {
|
||||||
|
setGroupOperatingFrequency(SoftApConfigurationCompat.channelToFrequency(operatingBand, oc))
|
||||||
}
|
}
|
||||||
setDeviceAddress(deviceAddress?.toPlatform())
|
}
|
||||||
}.build().run {
|
setDeviceAddress(deviceAddress?.toPlatform())
|
||||||
useParcel { p ->
|
}.build(), listener)
|
||||||
p.writeParcelable(this, 0)
|
|
||||||
val end = p.dataPosition()
|
|
||||||
p.setDataPosition(0)
|
|
||||||
val creator = p.readString()
|
|
||||||
val deviceAddress = p.readString()
|
|
||||||
val wps = p.readParcelable<WpsInfo>(javaClass.classLoader)
|
|
||||||
val long = p.readLong()
|
|
||||||
check(p.readString() == PLACEHOLDER_NETWORK_NAME)
|
|
||||||
check(p.readString() == passphrase)
|
|
||||||
val extrasLength = end - p.dataPosition()
|
|
||||||
check(extrasLength and 3 == 0) // parcel should be padded
|
|
||||||
if (extrasLength != 4) app.logEvent("p2p_config_extras_unexpected_length") {
|
|
||||||
param("length", extrasLength.toLong())
|
|
||||||
}
|
|
||||||
val extras = (0 until extrasLength / 4).map { p.readInt() }
|
|
||||||
p.setDataPosition(0)
|
|
||||||
p.writeString(creator)
|
|
||||||
p.writeString(deviceAddress)
|
|
||||||
p.writeParcelable(wps, 0)
|
|
||||||
p.writeLong(long)
|
|
||||||
p.writeString(networkName)
|
|
||||||
p.writeString(passphrase)
|
|
||||||
extras.forEach(p::writeInt)
|
|
||||||
p.setDataPosition(0)
|
|
||||||
p.readParcelable(javaClass.classLoader)
|
|
||||||
}
|
|
||||||
}, listener)
|
|
||||||
}
|
|
||||||
} catch (e: SecurityException) {
|
|
||||||
Timber.w(e)
|
|
||||||
startFailure(e.readableMessage)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@@ -512,7 +488,7 @@ class RepeaterService : Service(), CoroutineScope, WifiP2pManager.ChannelListene
|
|||||||
if (reason != WifiP2pManager.BUSY) {
|
if (reason != WifiP2pManager.BUSY) {
|
||||||
SmartSnackbar.make(formatReason(R.string.repeater_remove_group_failure, reason)).show()
|
SmartSnackbar.make(formatReason(R.string.repeater_remove_group_failure, reason)).show()
|
||||||
} // else assuming it's already gone
|
} // else assuming it's already gone
|
||||||
launch { cleanLocked() }
|
onSuccess()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,25 +12,29 @@ import be.mygod.vpnhotspot.App.Companion.app
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
object ServiceNotification {
|
object ServiceNotification {
|
||||||
private const val CHANNEL = "tethering"
|
private const val CHANNEL_ACTIVE = "tethering"
|
||||||
private const val CHANNEL_ID = 1
|
private const val CHANNEL_INACTIVE = "tethering-inactive"
|
||||||
|
private const val NOTIFICATION_ID = 1
|
||||||
|
|
||||||
private val deviceCountsMap = WeakHashMap<Service, Map<String, Int>>()
|
private val deviceCountsMap = WeakHashMap<Service, Map<String, Int>>()
|
||||||
private val inactiveMap = WeakHashMap<Service, List<String>>()
|
private val inactiveMap = WeakHashMap<Service, List<String>>()
|
||||||
private val manager = app.getSystemService<NotificationManager>()!!
|
private val manager = app.getSystemService<NotificationManager>()!!
|
||||||
|
|
||||||
private fun buildNotification(context: Context): Notification {
|
private fun buildNotification(context: Context): Notification {
|
||||||
val builder = NotificationCompat.Builder(context, CHANNEL)
|
|
||||||
.setWhen(0)
|
|
||||||
.setCategory(NotificationCompat.CATEGORY_SERVICE)
|
|
||||||
.setColor(ContextCompat.getColor(context, R.color.colorPrimary))
|
|
||||||
.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))
|
|
||||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
|
||||||
val deviceCounts = deviceCountsMap.values.flatMap { it.entries }.sortedBy { it.key }
|
val deviceCounts = deviceCountsMap.values.flatMap { it.entries }.sortedBy { it.key }
|
||||||
val inactive = inactiveMap.values.flatten()
|
val inactive = inactiveMap.values.flatten()
|
||||||
|
val isInactive = inactive.isNotEmpty() && deviceCounts.isEmpty()
|
||||||
|
val builder = NotificationCompat.Builder(context, if (isInactive) CHANNEL_INACTIVE else CHANNEL_ACTIVE).apply {
|
||||||
|
setWhen(0)
|
||||||
|
setCategory(NotificationCompat.CATEGORY_SERVICE)
|
||||||
|
color = ContextCompat.getColor(context, R.color.colorPrimary)
|
||||||
|
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
|
||||||
|
}
|
||||||
var lines = deviceCounts.map { (dev, size) ->
|
var lines = deviceCounts.map { (dev, size) ->
|
||||||
context.resources.getQuantityString(R.plurals.notification_connected_devices, size, size, dev)
|
context.resources.getQuantityString(R.plurals.notification_connected_devices, size, size, dev)
|
||||||
}
|
}
|
||||||
@@ -54,23 +58,28 @@ object ServiceNotification {
|
|||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
deviceCountsMap[service] = deviceCounts
|
deviceCountsMap[service] = deviceCounts
|
||||||
if (inactive.isEmpty()) inactiveMap.remove(service) else inactiveMap[service] = inactive
|
if (inactive.isEmpty()) inactiveMap.remove(service) else inactiveMap[service] = inactive
|
||||||
service.startForeground(CHANNEL_ID, buildNotification(service))
|
service.startForeground(NOTIFICATION_ID, buildNotification(service))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fun stopForeground(service: Service) = synchronized(this) {
|
fun stopForeground(service: Service) = synchronized(this) {
|
||||||
deviceCountsMap.remove(service)
|
deviceCountsMap.remove(service) ?: return@synchronized
|
||||||
if (deviceCountsMap.isEmpty()) service.stopForeground(true) else {
|
val shutdown = deviceCountsMap.isEmpty()
|
||||||
service.stopForeground(false)
|
service.stopForeground(shutdown)
|
||||||
manager.notify(CHANNEL_ID, buildNotification(service))
|
if (!shutdown) manager.notify(NOTIFICATION_ID, buildNotification(service))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateNotificationChannels() {
|
fun updateNotificationChannels() {
|
||||||
if (Build.VERSION.SDK_INT >= 26) @TargetApi(26) {
|
if (Build.VERSION.SDK_INT >= 26) @TargetApi(26) {
|
||||||
val tethering = NotificationChannel(CHANNEL,
|
NotificationChannel(CHANNEL_ACTIVE,
|
||||||
app.getText(R.string.notification_channel_tethering), NotificationManager.IMPORTANCE_LOW)
|
app.getText(R.string.notification_channel_tethering), NotificationManager.IMPORTANCE_LOW).apply {
|
||||||
tethering.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
|
lockscreenVisibility = Notification.VISIBILITY_PUBLIC
|
||||||
manager.createNotificationChannel(tethering)
|
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
|
// remove old service channels
|
||||||
manager.deleteNotificationChannel("hotspot")
|
manager.deleteNotificationChannel("hotspot")
|
||||||
manager.deleteNotificationChannel("repeater")
|
manager.deleteNotificationChannel("repeater")
|
||||||
|
|||||||
@@ -14,13 +14,12 @@ import be.mygod.vpnhotspot.net.monitor.FallbackUpstreamMonitor
|
|||||||
import be.mygod.vpnhotspot.net.monitor.IpMonitor
|
import be.mygod.vpnhotspot.net.monitor.IpMonitor
|
||||||
import be.mygod.vpnhotspot.net.monitor.UpstreamMonitor
|
import be.mygod.vpnhotspot.net.monitor.UpstreamMonitor
|
||||||
import be.mygod.vpnhotspot.net.wifi.WifiDoubleLock
|
import be.mygod.vpnhotspot.net.wifi.WifiDoubleLock
|
||||||
import be.mygod.vpnhotspot.preference.AlwaysAutoCompleteEditTextPreferenceDialogFragment
|
import be.mygod.vpnhotspot.preference.AutoCompleteNetworkPreferenceDialogFragment
|
||||||
import be.mygod.vpnhotspot.preference.SharedPreferenceDataStore
|
import be.mygod.vpnhotspot.preference.SharedPreferenceDataStore
|
||||||
import be.mygod.vpnhotspot.preference.SummaryFallbackProvider
|
import be.mygod.vpnhotspot.preference.SummaryFallbackProvider
|
||||||
import be.mygod.vpnhotspot.root.Dump
|
import be.mygod.vpnhotspot.root.Dump
|
||||||
import be.mygod.vpnhotspot.root.RootManager
|
import be.mygod.vpnhotspot.root.RootManager
|
||||||
import be.mygod.vpnhotspot.util.Services
|
import be.mygod.vpnhotspot.util.Services
|
||||||
import be.mygod.vpnhotspot.util.allInterfaceNames
|
|
||||||
import be.mygod.vpnhotspot.util.launchUrl
|
import be.mygod.vpnhotspot.util.launchUrl
|
||||||
import be.mygod.vpnhotspot.util.showAllowingStateLoss
|
import be.mygod.vpnhotspot.util.showAllowingStateLoss
|
||||||
import be.mygod.vpnhotspot.widget.SmartSnackbar
|
import be.mygod.vpnhotspot.widget.SmartSnackbar
|
||||||
@@ -144,16 +143,12 @@ class SettingsPreferenceFragment : PreferenceFragmentCompat() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDisplayPreferenceDialog(preference: Preference) {
|
override fun onDisplayPreferenceDialog(preference: Preference) = when (preference.key) {
|
||||||
when (preference.key) {
|
UpstreamMonitor.KEY, FallbackUpstreamMonitor.KEY ->
|
||||||
UpstreamMonitor.KEY, FallbackUpstreamMonitor.KEY ->
|
AutoCompleteNetworkPreferenceDialogFragment().apply {
|
||||||
AlwaysAutoCompleteEditTextPreferenceDialogFragment().apply {
|
setArguments(preference.key)
|
||||||
setArguments(preference.key, Services.connectivity.allNetworks.mapNotNull {
|
setTargetFragment(this@SettingsPreferenceFragment, 0)
|
||||||
Services.connectivity.getLinkProperties(it)?.allInterfaceNames
|
}.showAllowingStateLoss(parentFragmentManager, preference.key)
|
||||||
}.flatten().toTypedArray())
|
else -> super.onDisplayPreferenceDialog(preference)
|
||||||
setTargetFragment(this@SettingsPreferenceFragment, 0)
|
|
||||||
}.showAllowingStateLoss(parentFragmentManager, preference.key)
|
|
||||||
else -> super.onDisplayPreferenceDialog(preference)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,10 @@ import android.content.ComponentName
|
|||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
import android.content.ServiceConnection
|
import android.content.ServiceConnection
|
||||||
import android.net.wifi.p2p.WifiP2pDevice
|
import android.net.wifi.p2p.WifiP2pDevice
|
||||||
|
import android.os.Build
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
|
import android.os.Parcelable
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.lifecycle.DefaultLifecycleObserver
|
import androidx.lifecycle.DefaultLifecycleObserver
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
@@ -13,13 +16,19 @@ import be.mygod.vpnhotspot.App.Companion.app
|
|||||||
import be.mygod.vpnhotspot.RepeaterService
|
import be.mygod.vpnhotspot.RepeaterService
|
||||||
import be.mygod.vpnhotspot.net.IpNeighbour
|
import be.mygod.vpnhotspot.net.IpNeighbour
|
||||||
import be.mygod.vpnhotspot.net.MacAddressCompat
|
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
|
||||||
import be.mygod.vpnhotspot.net.TetheringManager.localOnlyTetheredIfaces
|
import be.mygod.vpnhotspot.net.TetheringManager.localOnlyTetheredIfaces
|
||||||
import be.mygod.vpnhotspot.net.TetheringManager.tetheredIfaces
|
import be.mygod.vpnhotspot.net.TetheringManager.tetheredIfaces
|
||||||
import be.mygod.vpnhotspot.net.monitor.IpNeighbourMonitor
|
import be.mygod.vpnhotspot.net.monitor.IpNeighbourMonitor
|
||||||
|
import be.mygod.vpnhotspot.net.wifi.WifiApManager
|
||||||
|
import be.mygod.vpnhotspot.net.wifi.WifiClient
|
||||||
|
import be.mygod.vpnhotspot.root.WifiApCommands
|
||||||
import be.mygod.vpnhotspot.util.broadcastReceiver
|
import be.mygod.vpnhotspot.util.broadcastReceiver
|
||||||
|
|
||||||
class ClientViewModel : ViewModel(), ServiceConnection, IpNeighbourMonitor.Callback, DefaultLifecycleObserver {
|
class ClientViewModel : ViewModel(), ServiceConnection, IpNeighbourMonitor.Callback, DefaultLifecycleObserver,
|
||||||
|
WifiApManager.SoftApCallbackCompat {
|
||||||
private var tetheredInterfaces = emptySet<String>()
|
private var tetheredInterfaces = emptySet<String>()
|
||||||
private val receiver = broadcastReceiver { _, intent ->
|
private val receiver = broadcastReceiver { _, intent ->
|
||||||
tetheredInterfaces = (intent.tetheredIfaces ?: return@broadcastReceiver).toSet() +
|
tetheredInterfaces = (intent.tetheredIfaces ?: return@broadcastReceiver).toSet() +
|
||||||
@@ -29,6 +38,7 @@ class ClientViewModel : ViewModel(), ServiceConnection, IpNeighbourMonitor.Callb
|
|||||||
|
|
||||||
private var repeater: RepeaterService.Binder? = null
|
private var repeater: RepeaterService.Binder? = null
|
||||||
private var p2p: Collection<WifiP2pDevice> = emptyList()
|
private var p2p: Collection<WifiP2pDevice> = emptyList()
|
||||||
|
private var wifiAp = emptyList<Pair<String, MacAddressCompat>>()
|
||||||
private var neighbours: Collection<IpNeighbour> = emptyList()
|
private var neighbours: Collection<IpNeighbour> = emptyList()
|
||||||
val clients = MutableLiveData<List<Client>>()
|
val clients = MutableLiveData<List<Client>>()
|
||||||
val fullMode = object : DefaultLifecycleObserver {
|
val fullMode = object : DefaultLifecycleObserver {
|
||||||
@@ -42,11 +52,18 @@ class ClientViewModel : ViewModel(), ServiceConnection, IpNeighbourMonitor.Callb
|
|||||||
|
|
||||||
private fun populateClients() {
|
private fun populateClients() {
|
||||||
val clients = HashMap<Pair<String, MacAddressCompat>, Client>()
|
val clients = HashMap<Pair<String, MacAddressCompat>, Client>()
|
||||||
val group = repeater?.group
|
repeater?.group?.`interface`?.let { p2pInterface ->
|
||||||
val p2pInterface = group?.`interface`
|
for (client in p2p) {
|
||||||
if (p2pInterface != null) {
|
val addr = MacAddressCompat.fromString(client.deviceAddress!!)
|
||||||
for (client in p2p) clients[p2pInterface to MacAddressCompat.fromString(client.deviceAddress)] =
|
clients[p2pInterface to addr] = object : Client(addr, p2pInterface) {
|
||||||
WifiP2pClient(p2pInterface, client)
|
override val icon: Int get() = TetherType.WIFI_P2P.icon
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (client in wifiAp) {
|
||||||
|
clients[client] = object : Client(client.second, client.first) {
|
||||||
|
override val icon: Int get() = TetherType.WIFI.icon
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for (neighbour in neighbours) {
|
for (neighbour in neighbours) {
|
||||||
val key = neighbour.dev to neighbour.lladdr
|
val key = neighbour.dev to neighbour.lladdr
|
||||||
@@ -70,8 +87,10 @@ class ClientViewModel : ViewModel(), ServiceConnection, IpNeighbourMonitor.Callb
|
|||||||
override fun onStart(owner: LifecycleOwner) {
|
override fun onStart(owner: LifecycleOwner) {
|
||||||
app.registerReceiver(receiver, IntentFilter(TetheringManager.ACTION_TETHER_STATE_CHANGED))
|
app.registerReceiver(receiver, IntentFilter(TetheringManager.ACTION_TETHER_STATE_CHANGED))
|
||||||
IpNeighbourMonitor.registerCallback(this, false)
|
IpNeighbourMonitor.registerCallback(this, false)
|
||||||
|
if (Build.VERSION.SDK_INT >= 31) WifiApCommands.registerSoftApCallback(this)
|
||||||
}
|
}
|
||||||
override fun onStop(owner: LifecycleOwner) {
|
override fun onStop(owner: LifecycleOwner) {
|
||||||
|
if (Build.VERSION.SDK_INT >= 31) WifiApCommands.unregisterSoftApCallback(this)
|
||||||
IpNeighbourMonitor.unregisterCallback(this)
|
IpNeighbourMonitor.unregisterCallback(this)
|
||||||
app.unregisterReceiver(receiver)
|
app.unregisterReceiver(receiver)
|
||||||
}
|
}
|
||||||
@@ -94,4 +113,12 @@ class ClientViewModel : ViewModel(), ServiceConnection, IpNeighbourMonitor.Callb
|
|||||||
this.neighbours = neighbours
|
this.neighbours = neighbours
|
||||||
populateClients()
|
populateClients()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RequiresApi(31)
|
||||||
|
override fun onConnectedClientsChanged(clients: List<Parcelable>) {
|
||||||
|
wifiAp = clients.mapNotNull {
|
||||||
|
val client = WifiClient(it)
|
||||||
|
client.apInstanceIdentifier?.run { this to client.macAddress.toCompat() }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ import be.mygod.vpnhotspot.net.monitor.TrafficRecorder
|
|||||||
import be.mygod.vpnhotspot.room.AppDatabase
|
import be.mygod.vpnhotspot.room.AppDatabase
|
||||||
import be.mygod.vpnhotspot.room.ClientStats
|
import be.mygod.vpnhotspot.room.ClientStats
|
||||||
import be.mygod.vpnhotspot.room.TrafficRecord
|
import be.mygod.vpnhotspot.room.TrafficRecord
|
||||||
import be.mygod.vpnhotspot.util.SpanFormatter
|
import be.mygod.vpnhotspot.util.format
|
||||||
import be.mygod.vpnhotspot.util.showAllowingStateLoss
|
import be.mygod.vpnhotspot.util.showAllowingStateLoss
|
||||||
import be.mygod.vpnhotspot.util.toPluralInt
|
import be.mygod.vpnhotspot.util.toPluralInt
|
||||||
import be.mygod.vpnhotspot.widget.SmartSnackbar
|
import be.mygod.vpnhotspot.widget.SmartSnackbar
|
||||||
@@ -82,10 +82,11 @@ class ClientsFragment : Fragment() {
|
|||||||
data class StatsArg(val title: CharSequence, val stats: ClientStats) : Parcelable
|
data class StatsArg(val title: CharSequence, val stats: ClientStats) : Parcelable
|
||||||
class StatsDialogFragment : AlertDialogFragment<StatsArg, Empty>() {
|
class StatsDialogFragment : AlertDialogFragment<StatsArg, Empty>() {
|
||||||
override fun AlertDialog.Builder.prepare(listener: DialogInterface.OnClickListener) {
|
override fun AlertDialog.Builder.prepare(listener: DialogInterface.OnClickListener) {
|
||||||
setTitle(SpanFormatter.format(getText(R.string.clients_stats_title), arg.title))
|
|
||||||
val context = context
|
val context = context
|
||||||
val resources = resources
|
val resources = resources
|
||||||
val format = NumberFormat.getIntegerInstance(resources.configuration.locale)
|
val locale = resources.configuration.locale
|
||||||
|
setTitle(getText(R.string.clients_stats_title).format(locale, arg.title))
|
||||||
|
val format = NumberFormat.getIntegerInstance(locale)
|
||||||
setMessage("%s\n%s\n%s".format(
|
setMessage("%s\n%s\n%s".format(
|
||||||
resources.getQuantityString(R.plurals.clients_stats_message_1, arg.stats.count.toPluralInt(),
|
resources.getQuantityString(R.plurals.clients_stats_message_1, arg.stats.count.toPluralInt(),
|
||||||
format.format(arg.stats.count), DateUtils.formatDateTime(context, arg.stats.timestamp,
|
format.format(arg.stats.count), DateUtils.formatDateTime(context, arg.stats.timestamp,
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
package be.mygod.vpnhotspot.client
|
|
||||||
|
|
||||||
import android.net.wifi.p2p.WifiP2pDevice
|
|
||||||
import be.mygod.vpnhotspot.net.MacAddressCompat
|
|
||||||
import be.mygod.vpnhotspot.net.TetherType
|
|
||||||
|
|
||||||
class WifiP2pClient(p2pInterface: String, p2p: WifiP2pDevice) :
|
|
||||||
Client(MacAddressCompat.fromString(p2p.deviceAddress!!), p2pInterface) {
|
|
||||||
override val icon: Int get() = TetherType.WIFI_P2P.icon
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package be.mygod.vpnhotspot.manage
|
package be.mygod.vpnhotspot.manage
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.annotation.TargetApi
|
import android.annotation.TargetApi
|
||||||
import android.bluetooth.BluetoothAdapter
|
import android.bluetooth.BluetoothAdapter
|
||||||
import android.bluetooth.BluetoothProfile
|
import android.bluetooth.BluetoothProfile
|
||||||
@@ -9,7 +10,6 @@ import android.content.Intent
|
|||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.core.os.BuildCompat
|
|
||||||
import be.mygod.vpnhotspot.App.Companion.app
|
import be.mygod.vpnhotspot.App.Companion.app
|
||||||
import be.mygod.vpnhotspot.net.TetheringManager
|
import be.mygod.vpnhotspot.net.TetheringManager
|
||||||
import be.mygod.vpnhotspot.util.broadcastReceiver
|
import be.mygod.vpnhotspot.util.broadcastReceiver
|
||||||
@@ -18,7 +18,7 @@ import be.mygod.vpnhotspot.widget.SmartSnackbar
|
|||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.lang.reflect.InvocationTargetException
|
import java.lang.reflect.InvocationTargetException
|
||||||
|
|
||||||
class BluetoothTethering(context: Context, val stateListener: () -> Unit) :
|
class BluetoothTethering(context: Context, private val adapter: BluetoothAdapter, val stateListener: () -> Unit) :
|
||||||
BluetoothProfile.ServiceListener, AutoCloseable {
|
BluetoothProfile.ServiceListener, AutoCloseable {
|
||||||
companion object : BroadcastReceiver() {
|
companion object : BroadcastReceiver() {
|
||||||
/**
|
/**
|
||||||
@@ -26,17 +26,9 @@ class BluetoothTethering(context: Context, val stateListener: () -> Unit) :
|
|||||||
*/
|
*/
|
||||||
private const val PAN = 5
|
private const val PAN = 5
|
||||||
private val clazz by lazy { Class.forName("android.bluetooth.BluetoothPan") }
|
private val clazz by lazy { Class.forName("android.bluetooth.BluetoothPan") }
|
||||||
private val constructor by lazy {
|
|
||||||
clazz.getDeclaredConstructor(Context::class.java, BluetoothProfile.ServiceListener::class.java).apply {
|
|
||||||
isAccessible = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private val isTetheringOn by lazy { clazz.getDeclaredMethod("isTetheringOn") }
|
private val isTetheringOn by lazy { clazz.getDeclaredMethod("isTetheringOn") }
|
||||||
|
|
||||||
fun pan(context: Context, serviceListener: BluetoothProfile.ServiceListener) =
|
private val BluetoothProfile.isTetheringOn get() = isTetheringOn(this) as Boolean
|
||||||
constructor.newInstance(context, serviceListener) as BluetoothProfile
|
|
||||||
val BluetoothProfile.isTetheringOn get() = isTetheringOn(this) as Boolean
|
|
||||||
fun BluetoothProfile.closePan() = BluetoothAdapter.getDefaultAdapter()!!.closeProfileProxy(PAN, this)
|
|
||||||
|
|
||||||
private fun registerBluetoothStateListener(receiver: BroadcastReceiver) =
|
private fun registerBluetoothStateListener(receiver: BroadcastReceiver) =
|
||||||
app.registerReceiver(receiver, IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED))
|
app.registerReceiver(receiver, IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED))
|
||||||
@@ -58,28 +50,12 @@ class BluetoothTethering(context: Context, val stateListener: () -> Unit) :
|
|||||||
pendingCallback = null
|
pendingCallback = null
|
||||||
app.unregisterReceiver(this)
|
app.unregisterReceiver(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* https://android.googlesource.com/platform/packages/apps/Settings/+/b1af85d/src/com/android/settings/TetherSettings.java#384
|
|
||||||
*/
|
|
||||||
@RequiresApi(24)
|
|
||||||
fun start(callback: TetheringManager.StartTetheringCallback) {
|
|
||||||
if (pendingCallback != null) return
|
|
||||||
val adapter = BluetoothAdapter.getDefaultAdapter()
|
|
||||||
try {
|
|
||||||
if (adapter?.state == BluetoothAdapter.STATE_OFF) {
|
|
||||||
registerBluetoothStateListener(this)
|
|
||||||
pendingCallback = callback
|
|
||||||
adapter.enable()
|
|
||||||
} else TetheringManager.startTethering(TetheringManager.TETHERING_BLUETOOTH, true, callback)
|
|
||||||
} catch (e: SecurityException) {
|
|
||||||
SmartSnackbar.make(e.readableMessage).shortToast().show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var proxyCreated = false
|
||||||
private var connected = false
|
private var connected = false
|
||||||
private var pan: BluetoothProfile? = null
|
private var pan: BluetoothProfile? = null
|
||||||
|
private var stoppedByUser = false
|
||||||
var activeFailureCause: Throwable? = null
|
var activeFailureCause: Throwable? = null
|
||||||
/**
|
/**
|
||||||
* Based on: https://android.googlesource.com/platform/packages/apps/Settings/+/78d5efd/src/com/android/settings/TetherSettings.java
|
* Based on: https://android.googlesource.com/platform/packages/apps/Settings/+/78d5efd/src/com/android/settings/TetherSettings.java
|
||||||
@@ -88,7 +64,7 @@ class BluetoothTethering(context: Context, val stateListener: () -> Unit) :
|
|||||||
val pan = pan ?: return null
|
val pan = pan ?: return null
|
||||||
if (!connected) return null
|
if (!connected) return null
|
||||||
activeFailureCause = null
|
activeFailureCause = null
|
||||||
return BluetoothAdapter.getDefaultAdapter()?.state == BluetoothAdapter.STATE_ON && try {
|
val on = adapter.state == BluetoothAdapter.STATE_ON && try {
|
||||||
pan.isTetheringOn
|
pan.isTetheringOn
|
||||||
} catch (e: InvocationTargetException) {
|
} catch (e: InvocationTargetException) {
|
||||||
activeFailureCause = e.cause ?: e
|
activeFailureCause = e.cause ?: e
|
||||||
@@ -96,16 +72,21 @@ class BluetoothTethering(context: Context, val stateListener: () -> Unit) :
|
|||||||
else Timber.w(e)
|
else Timber.w(e)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
return if (stoppedByUser) {
|
||||||
|
if (!on) stoppedByUser = false
|
||||||
|
false
|
||||||
|
} else on
|
||||||
}
|
}
|
||||||
|
|
||||||
private val receiver = broadcastReceiver { _, _ -> stateListener() }
|
private val receiver = broadcastReceiver { _, _ -> stateListener() }
|
||||||
|
|
||||||
fun ensureInit(context: Context) {
|
fun ensureInit(context: Context) {
|
||||||
if (pan == null && BluetoothAdapter.getDefaultAdapter() != null) try {
|
activeFailureCause = null
|
||||||
pan = pan(context, this)
|
if (!proxyCreated) try {
|
||||||
} catch (e: ReflectiveOperationException) {
|
check(adapter.getProfileProxy(context, this, PAN))
|
||||||
if (e.cause is SecurityException && BuildCompat.isAtLeastS()) Timber.d(e.readableMessage)
|
proxyCreated = true
|
||||||
else Timber.w(e)
|
} catch (e: SecurityException) {
|
||||||
|
if (Build.VERSION.SDK_INT >= 31) Timber.d(e.readableMessage) else Timber.w(e)
|
||||||
activeFailureCause = e
|
activeFailureCause = e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -116,13 +97,39 @@ class BluetoothTethering(context: Context, val stateListener: () -> Unit) :
|
|||||||
|
|
||||||
override fun onServiceDisconnected(profile: Int) {
|
override fun onServiceDisconnected(profile: Int) {
|
||||||
connected = false
|
connected = false
|
||||||
|
stoppedByUser = false
|
||||||
}
|
}
|
||||||
override fun onServiceConnected(profile: Int, proxy: BluetoothProfile) {
|
override fun onServiceConnected(profile: Int, proxy: BluetoothProfile) {
|
||||||
|
pan = proxy
|
||||||
connected = true
|
connected = true
|
||||||
stateListener()
|
stateListener()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
if (pendingCallback == null) try {
|
||||||
|
if (adapter.state == BluetoothAdapter.STATE_OFF) {
|
||||||
|
registerBluetoothStateListener(BluetoothTethering)
|
||||||
|
pendingCallback = callback
|
||||||
|
adapter.enable()
|
||||||
|
} else TetheringManager.startTethering(TetheringManager.TETHERING_BLUETOOTH, true, callback)
|
||||||
|
} catch (e: SecurityException) {
|
||||||
|
SmartSnackbar.make(e.readableMessage).shortToast().show()
|
||||||
|
pendingCallback = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@RequiresApi(24)
|
||||||
|
fun stop(callback: (Exception) -> Unit) {
|
||||||
|
TetheringManager.stopTethering(TetheringManager.TETHERING_BLUETOOTH, callback)
|
||||||
|
stoppedByUser = true
|
||||||
|
}
|
||||||
|
|
||||||
override fun close() {
|
override fun close() {
|
||||||
app.unregisterReceiver(receiver)
|
app.unregisterReceiver(receiver)
|
||||||
pan?.closePan()
|
adapter.closeProfileProxy(PAN, pan)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
package be.mygod.vpnhotspot.manage
|
package be.mygod.vpnhotspot.manage
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.annotation.TargetApi
|
|
||||||
import android.content.*
|
import android.content.*
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import be.mygod.vpnhotspot.App.Companion.app
|
import be.mygod.vpnhotspot.App.Companion.app
|
||||||
import be.mygod.vpnhotspot.LocalOnlyHotspotService
|
import be.mygod.vpnhotspot.LocalOnlyHotspotService
|
||||||
@@ -18,7 +18,7 @@ import be.mygod.vpnhotspot.util.formatAddresses
|
|||||||
import be.mygod.vpnhotspot.widget.SmartSnackbar
|
import be.mygod.vpnhotspot.widget.SmartSnackbar
|
||||||
import java.net.NetworkInterface
|
import java.net.NetworkInterface
|
||||||
|
|
||||||
@TargetApi(26)
|
@RequiresApi(26)
|
||||||
class LocalOnlyHotspotManager(private val parent: TetheringFragment) : Manager(), ServiceConnection {
|
class LocalOnlyHotspotManager(private val parent: TetheringFragment) : Manager(), ServiceConnection {
|
||||||
companion object {
|
companion object {
|
||||||
val permission = if (Build.VERSION.SDK_INT >= 29) {
|
val permission = if (Build.VERSION.SDK_INT >= 29) {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import android.content.ComponentName
|
|||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.ServiceConnection
|
import android.content.ServiceConnection
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import android.net.wifi.SoftApConfiguration
|
import android.net.wifi.SoftApConfiguration
|
||||||
import android.net.wifi.p2p.WifiP2pGroup
|
import android.net.wifi.p2p.WifiP2pGroup
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
@@ -30,6 +29,7 @@ import be.mygod.vpnhotspot.net.MacAddressCompat
|
|||||||
import be.mygod.vpnhotspot.net.wifi.P2pSupplicantConfiguration
|
import be.mygod.vpnhotspot.net.wifi.P2pSupplicantConfiguration
|
||||||
import be.mygod.vpnhotspot.net.wifi.SoftApConfigurationCompat
|
import be.mygod.vpnhotspot.net.wifi.SoftApConfigurationCompat
|
||||||
import be.mygod.vpnhotspot.net.wifi.WifiApDialogFragment
|
import be.mygod.vpnhotspot.net.wifi.WifiApDialogFragment
|
||||||
|
import be.mygod.vpnhotspot.net.wifi.WifiApManager
|
||||||
import be.mygod.vpnhotspot.util.ServiceForegroundConnector
|
import be.mygod.vpnhotspot.util.ServiceForegroundConnector
|
||||||
import be.mygod.vpnhotspot.util.formatAddresses
|
import be.mygod.vpnhotspot.util.formatAddresses
|
||||||
import be.mygod.vpnhotspot.util.showAllowingStateLoss
|
import be.mygod.vpnhotspot.util.showAllowingStateLoss
|
||||||
@@ -89,11 +89,7 @@ class RepeaterManager(private val parent: TetheringFragment) : Manager(), Servic
|
|||||||
when (binder?.service?.status) {
|
when (binder?.service?.status) {
|
||||||
RepeaterService.Status.IDLE -> if (Build.VERSION.SDK_INT < 29) parent.requireContext().let { context ->
|
RepeaterService.Status.IDLE -> if (Build.VERSION.SDK_INT < 29) parent.requireContext().let { context ->
|
||||||
ContextCompat.startForegroundService(context, Intent(context, RepeaterService::class.java))
|
ContextCompat.startForegroundService(context, Intent(context, RepeaterService::class.java))
|
||||||
} else if (parent.requireContext().checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) ==
|
} else parent.startRepeater.launch(Manifest.permission.ACCESS_FINE_LOCATION)
|
||||||
PackageManager.PERMISSION_GRANTED ||
|
|
||||||
parent.shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION)) {
|
|
||||||
parent.startRepeater.launch(Manifest.permission.ACCESS_FINE_LOCATION)
|
|
||||||
} else SmartSnackbar.make(R.string.repeater_missing_location_permissions).shortToast().show()
|
|
||||||
RepeaterService.Status.ACTIVE -> binder.shutdown()
|
RepeaterService.Status.ACTIVE -> binder.shutdown()
|
||||||
else -> { }
|
else -> { }
|
||||||
}
|
}
|
||||||
@@ -192,23 +188,23 @@ class RepeaterManager(private val parent: TetheringFragment) : Manager(), Servic
|
|||||||
return SoftApConfigurationCompat(
|
return SoftApConfigurationCompat(
|
||||||
ssid = networkName,
|
ssid = networkName,
|
||||||
passphrase = passphrase,
|
passphrase = passphrase,
|
||||||
band = RepeaterService.operatingBand,
|
|
||||||
channel = RepeaterService.operatingChannel,
|
|
||||||
securityType = SoftApConfiguration.SECURITY_TYPE_WPA2_PSK, // is not actually used
|
securityType = SoftApConfiguration.SECURITY_TYPE_WPA2_PSK, // is not actually used
|
||||||
isAutoShutdownEnabled = RepeaterService.isAutoShutdownEnabled,
|
isAutoShutdownEnabled = RepeaterService.isAutoShutdownEnabled,
|
||||||
shutdownTimeoutMillis = RepeaterService.shutdownTimeoutMillis).apply {
|
shutdownTimeoutMillis = RepeaterService.shutdownTimeoutMillis).apply {
|
||||||
bssid = RepeaterService.deviceAddress
|
bssid = RepeaterService.deviceAddress
|
||||||
|
setChannel(RepeaterService.operatingChannel, RepeaterService.operatingBand)
|
||||||
|
setMacRandomizationEnabled(WifiApManager.p2pMacRandomizationSupported)
|
||||||
} to false
|
} to false
|
||||||
}
|
}
|
||||||
} else binder?.let { binder ->
|
} else binder?.let { binder ->
|
||||||
val group = binder.group ?: binder.fetchPersistentGroup().let { binder.group }
|
val group = binder.group ?: binder.fetchPersistentGroup().let { binder.group }
|
||||||
if (group != null) return SoftApConfigurationCompat(
|
if (group != null) return SoftApConfigurationCompat(
|
||||||
ssid = group.networkName,
|
ssid = group.networkName,
|
||||||
channel = RepeaterService.operatingChannel,
|
|
||||||
band = SoftApConfigurationCompat.BAND_ANY,
|
|
||||||
securityType = SoftApConfiguration.SECURITY_TYPE_WPA2_PSK, // is not actually used
|
securityType = SoftApConfiguration.SECURITY_TYPE_WPA2_PSK, // is not actually used
|
||||||
isAutoShutdownEnabled = RepeaterService.isAutoShutdownEnabled,
|
isAutoShutdownEnabled = RepeaterService.isAutoShutdownEnabled,
|
||||||
shutdownTimeoutMillis = RepeaterService.shutdownTimeoutMillis).run {
|
shutdownTimeoutMillis = RepeaterService.shutdownTimeoutMillis).run {
|
||||||
|
setChannel(RepeaterService.operatingChannel)
|
||||||
|
setMacRandomizationEnabled(WifiApManager.p2pMacRandomizationSupported)
|
||||||
try {
|
try {
|
||||||
val config = P2pSupplicantConfiguration(group)
|
val config = P2pSupplicantConfiguration(group)
|
||||||
config.init(binder.obtainDeviceAddress()?.toString())
|
config.init(binder.obtainDeviceAddress()?.toString())
|
||||||
@@ -230,6 +226,7 @@ class RepeaterManager(private val parent: TetheringFragment) : Manager(), Servic
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
private suspend fun updateConfiguration(config: SoftApConfigurationCompat) {
|
private suspend fun updateConfiguration(config: SoftApConfigurationCompat) {
|
||||||
|
val (band, channel) = config.requireSingleBand()
|
||||||
if (RepeaterService.safeMode) {
|
if (RepeaterService.safeMode) {
|
||||||
RepeaterService.networkName = config.ssid
|
RepeaterService.networkName = config.ssid
|
||||||
RepeaterService.deviceAddress = config.bssid
|
RepeaterService.deviceAddress = config.bssid
|
||||||
@@ -246,8 +243,8 @@ class RepeaterManager(private val parent: TetheringFragment) : Manager(), Servic
|
|||||||
}
|
}
|
||||||
holder.config = null
|
holder.config = null
|
||||||
}
|
}
|
||||||
RepeaterService.operatingBand = config.band
|
RepeaterService.operatingBand = band
|
||||||
RepeaterService.operatingChannel = config.channel
|
RepeaterService.operatingChannel = channel
|
||||||
RepeaterService.isAutoShutdownEnabled = config.isAutoShutdownEnabled
|
RepeaterService.isAutoShutdownEnabled = config.isAutoShutdownEnabled
|
||||||
RepeaterService.shutdownTimeoutMillis = config.shutdownTimeoutMillis
|
RepeaterService.shutdownTimeoutMillis = config.shutdownTimeoutMillis
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,40 +2,41 @@ package be.mygod.vpnhotspot.manage
|
|||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.annotation.TargetApi
|
import android.annotation.TargetApi
|
||||||
import android.content.ClipData
|
import android.bluetooth.BluetoothAdapter
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.net.MacAddress
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import android.os.Parcelable
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
|
import android.text.SpannableStringBuilder
|
||||||
|
import android.text.format.DateUtils
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.core.os.BuildCompat
|
|
||||||
import androidx.core.view.updatePaddingRelative
|
import androidx.core.view.updatePaddingRelative
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import androidx.lifecycle.DefaultLifecycleObserver
|
import androidx.lifecycle.DefaultLifecycleObserver
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import be.mygod.vpnhotspot.App.Companion.app
|
import be.mygod.vpnhotspot.App.Companion.app
|
||||||
import be.mygod.vpnhotspot.BuildConfig
|
|
||||||
import be.mygod.vpnhotspot.MainActivity
|
import be.mygod.vpnhotspot.MainActivity
|
||||||
import be.mygod.vpnhotspot.R
|
import be.mygod.vpnhotspot.R
|
||||||
import be.mygod.vpnhotspot.databinding.ListitemInterfaceBinding
|
import be.mygod.vpnhotspot.databinding.ListitemInterfaceBinding
|
||||||
import be.mygod.vpnhotspot.net.TetherType
|
import be.mygod.vpnhotspot.net.TetherType
|
||||||
import be.mygod.vpnhotspot.net.TetheringManager
|
import be.mygod.vpnhotspot.net.TetheringManager
|
||||||
import be.mygod.vpnhotspot.net.wifi.SoftApConfigurationCompat
|
import be.mygod.vpnhotspot.net.wifi.*
|
||||||
import be.mygod.vpnhotspot.net.wifi.WifiApManager
|
import be.mygod.vpnhotspot.net.wifi.WifiApManager.wifiApState
|
||||||
import be.mygod.vpnhotspot.root.WifiApCommands
|
import be.mygod.vpnhotspot.root.WifiApCommands
|
||||||
import be.mygod.vpnhotspot.util.readableMessage
|
import be.mygod.vpnhotspot.util.*
|
||||||
import be.mygod.vpnhotspot.widget.SmartSnackbar
|
import be.mygod.vpnhotspot.widget.SmartSnackbar
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.lang.reflect.InvocationTargetException
|
import java.lang.reflect.InvocationTargetException
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
sealed class TetherManager(protected val parent: TetheringFragment) : Manager(),
|
sealed class TetherManager(protected val parent: TetheringFragment) : Manager(),
|
||||||
TetheringManager.StartTetheringCallback {
|
TetheringManager.StartTetheringCallback {
|
||||||
@@ -63,12 +64,16 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(),
|
|||||||
} catch (e: RuntimeException) {
|
} catch (e: RuntimeException) {
|
||||||
app.logEvent("manage_write_settings") { param("message", e.toString()) }
|
app.logEvent("manage_write_settings") { param("message", e.toString()) }
|
||||||
}
|
}
|
||||||
if (manager.isStarted) try {
|
when (manager.isStarted) {
|
||||||
manager.stop()
|
true -> try {
|
||||||
} catch (e: InvocationTargetException) {
|
manager.stop()
|
||||||
if (e.targetException !is SecurityException) Timber.w(e)
|
} catch (e: InvocationTargetException) {
|
||||||
manager.onException(e)
|
if (e.targetException !is SecurityException) Timber.w(e)
|
||||||
} else manager.start()
|
manager.onException(e)
|
||||||
|
}
|
||||||
|
false -> manager.start()
|
||||||
|
null -> manager.onClickNull()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,13 +84,13 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(),
|
|||||||
override val icon get() = tetherType.icon
|
override val icon get() = tetherType.icon
|
||||||
override val title get() = this@TetherManager.title
|
override val title get() = this@TetherManager.title
|
||||||
override val text get() = this@TetherManager.text
|
override val text get() = this@TetherManager.text
|
||||||
override val active get() = isStarted
|
override val active get() = isStarted == true
|
||||||
}
|
}
|
||||||
|
|
||||||
val data = Data()
|
val data = Data()
|
||||||
abstract val title: CharSequence
|
abstract val title: CharSequence
|
||||||
abstract val tetherType: TetherType
|
abstract val tetherType: TetherType
|
||||||
open val isStarted get() = parent.enabledTypes.contains(tetherType)
|
open val isStarted: Boolean? get() = parent.enabledTypes.contains(tetherType)
|
||||||
protected open val text: CharSequence get() = baseError ?: ""
|
protected open val text: CharSequence get() = baseError ?: ""
|
||||||
|
|
||||||
protected var baseError: String? = null
|
protected var baseError: String? = null
|
||||||
@@ -93,6 +98,7 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(),
|
|||||||
|
|
||||||
protected abstract fun start()
|
protected abstract fun start()
|
||||||
protected abstract fun stop()
|
protected abstract fun stop()
|
||||||
|
protected open fun onClickNull(): Unit = throw UnsupportedOperationException()
|
||||||
|
|
||||||
override fun onTetheringStarted() = data.notifyChange()
|
override fun onTetheringStarted() = data.notifyChange()
|
||||||
override fun onTetheringFailed(error: Int?) {
|
override fun onTetheringFailed(error: Int?) {
|
||||||
@@ -119,11 +125,13 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(),
|
|||||||
(viewHolder as ViewHolder).manager = this
|
(viewHolder as ViewHolder).manager = this
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateErrorMessage(errored: List<String>) {
|
fun updateErrorMessage(errored: List<String>, lastErrors: Map<String, Int>) {
|
||||||
val interested = errored.filter { TetherType.ofInterface(it) == tetherType }
|
val interested = errored.filter { TetherType.ofInterface(it) == tetherType }
|
||||||
baseError = if (interested.isEmpty()) null else interested.joinToString("\n") { iface ->
|
baseError = if (interested.isEmpty()) null else interested.joinToString("\n") { iface ->
|
||||||
"$iface: " + try {
|
"$iface: " + try {
|
||||||
TetheringManager.tetherErrorLookup(TetheringManager.getLastTetherError(iface))
|
TetheringManager.tetherErrorLookup(if (Build.VERSION.SDK_INT < 30) @Suppress("DEPRECATION") {
|
||||||
|
TetheringManager.getLastTetherError(iface)
|
||||||
|
} else lastErrors[iface] ?: 0)
|
||||||
} catch (e: InvocationTargetException) {
|
} catch (e: InvocationTargetException) {
|
||||||
if (Build.VERSION.SDK_INT !in 24..25 || e.cause !is SecurityException) Timber.w(e) else Timber.d(e)
|
if (Build.VERSION.SDK_INT !in 24..25 || e.cause !is SecurityException) Timber.w(e) else Timber.d(e)
|
||||||
e.readableMessage
|
e.readableMessage
|
||||||
@@ -135,77 +143,156 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(),
|
|||||||
@RequiresApi(24)
|
@RequiresApi(24)
|
||||||
class Wifi(parent: TetheringFragment) : TetherManager(parent), DefaultLifecycleObserver,
|
class Wifi(parent: TetheringFragment) : TetherManager(parent), DefaultLifecycleObserver,
|
||||||
WifiApManager.SoftApCallbackCompat {
|
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 failureReason: Int? = null
|
||||||
private var numClients: Int? = null
|
private var numClients: Int? = null
|
||||||
private var frequency = 0
|
private var info = emptyList<Parcelable>()
|
||||||
private var bandwidth = WifiApManager.CHANNEL_WIDTH_INVALID
|
private var capability: Parcelable? = null
|
||||||
private var capability: Pair<Int, Long>? = null
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (Build.VERSION.SDK_INT >= 28) parent.viewLifecycleOwner.lifecycle.addObserver(this)
|
if (Build.VERSION.SDK_INT >= 23) parent.viewLifecycleOwner.lifecycle.addObserver(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
@TargetApi(28)
|
|
||||||
override fun onStart(owner: LifecycleOwner) {
|
override fun onStart(owner: LifecycleOwner) {
|
||||||
WifiApCommands.registerSoftApCallback(this)
|
if (Build.VERSION.SDK_INT < 28) {
|
||||||
|
parent.requireContext().registerReceiver(receiver,
|
||||||
|
IntentFilter(WifiApManager.WIFI_AP_STATE_CHANGED_ACTION))
|
||||||
|
} else WifiApCommands.registerSoftApCallback(this)
|
||||||
}
|
}
|
||||||
@TargetApi(28)
|
|
||||||
override fun onStop(owner: LifecycleOwner) {
|
override fun onStop(owner: LifecycleOwner) {
|
||||||
WifiApCommands.unregisterSoftApCallback(this)
|
if (Build.VERSION.SDK_INT < 28) {
|
||||||
|
parent.requireContext().unregisterReceiver(receiver)
|
||||||
|
} else WifiApCommands.unregisterSoftApCallback(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStateChanged(state: Int, failureReason: Int) {
|
override fun onStateChanged(state: Int, failureReason: Int) {
|
||||||
if (state < 10 || state > 14) {
|
if (!WifiApManager.checkWifiApState(state)) return
|
||||||
Timber.w(Exception("Unknown state $state, $failureReason"))
|
this.failureReason = if (state == WifiApManager.WIFI_AP_STATE_FAILED) failureReason else null
|
||||||
return
|
|
||||||
}
|
|
||||||
this.failureReason = if (state == 14) failureReason else null // WIFI_AP_STATE_FAILED
|
|
||||||
data.notifyChange()
|
data.notifyChange()
|
||||||
}
|
}
|
||||||
override fun onNumClientsChanged(numClients: Int) {
|
override fun onNumClientsChanged(numClients: Int) {
|
||||||
this.numClients = numClients
|
this.numClients = numClients
|
||||||
if (Build.VERSION.SDK_INT >= 30) data.notifyChange() // only emits when onCapabilityChanged can be called
|
|
||||||
}
|
|
||||||
override fun onInfoChanged(frequency: Int, bandwidth: Int) {
|
|
||||||
this.frequency = frequency
|
|
||||||
this.bandwidth = bandwidth
|
|
||||||
data.notifyChange()
|
data.notifyChange()
|
||||||
}
|
}
|
||||||
override fun onCapabilityChanged(maxSupportedClients: Int, supportedFeatures: Long) {
|
override fun onInfoChanged(info: List<Parcelable>) {
|
||||||
capability = maxSupportedClients to supportedFeatures
|
this.info = info
|
||||||
data.notifyChange()
|
data.notifyChange()
|
||||||
}
|
}
|
||||||
override fun onBlockedClientConnecting(client: MacAddress, blockedReason: Int) {
|
override fun onCapabilityChanged(capability: Parcelable) {
|
||||||
val reason = WifiApManager.clientBlockLookup(blockedReason, true)
|
this.capability = capability
|
||||||
Timber.i("$client blocked from connecting: $reason ($blockedReason)")
|
data.notifyChange()
|
||||||
SmartSnackbar.make(parent.getString(R.string.tethering_manage_wifi_client_blocked, client, reason)).apply {
|
|
||||||
action(R.string.tethering_manage_wifi_copy_mac) {
|
|
||||||
app.clipboard.setPrimaryClip(ClipData.newPlainText(null, client.toString()))
|
|
||||||
}
|
|
||||||
}.show()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override val title get() = parent.getString(R.string.tethering_manage_wifi)
|
override val title get() = parent.getString(R.string.tethering_manage_wifi)
|
||||||
override val tetherType get() = TetherType.WIFI
|
override val tetherType get() = TetherType.WIFI
|
||||||
override val type get() = VIEW_TYPE_WIFI
|
override val type get() = VIEW_TYPE_WIFI
|
||||||
override val text get() = listOfNotNull(failureReason?.let { WifiApManager.failureReasonLookup(it) }, baseError,
|
|
||||||
if (frequency != 0 || bandwidth != WifiApManager.CHANNEL_WIDTH_INVALID) {
|
@TargetApi(30)
|
||||||
parent.getString(R.string.tethering_manage_wifi_info, frequency,
|
private fun formatCapability(locale: Locale) = capability?.let { parcel ->
|
||||||
SoftApConfigurationCompat.frequencyToChannel(frequency),
|
val capability = SoftApCapability(parcel)
|
||||||
WifiApManager.channelWidthLookup(bandwidth, true))
|
val numClients = numClients
|
||||||
} else null,
|
val maxClients = capability.maxSupportedClients
|
||||||
capability?.let { (maxSupportedClients, supportedFeatures) ->
|
var features = capability.supportedFeatures
|
||||||
app.resources.getQuantityString(R.plurals.tethering_manage_wifi_capabilities, maxSupportedClients,
|
if (Build.VERSION.SDK_INT >= 31) for ((flag, band) in arrayOf(
|
||||||
numClients ?: "?", maxSupportedClients, sequence {
|
SoftApCapability.SOFTAP_FEATURE_BAND_24G_SUPPORTED to SoftApConfigurationCompat.BAND_2GHZ,
|
||||||
var features = supportedFeatures
|
SoftApCapability.SOFTAP_FEATURE_BAND_5G_SUPPORTED to SoftApConfigurationCompat.BAND_5GHZ,
|
||||||
if (features == 0L) yield(parent.getString(R.string.tethering_manage_wifi_no_features))
|
SoftApCapability.SOFTAP_FEATURE_BAND_6G_SUPPORTED to SoftApConfigurationCompat.BAND_6GHZ,
|
||||||
else while (features != 0L) {
|
SoftApCapability.SOFTAP_FEATURE_BAND_60G_SUPPORTED to SoftApConfigurationCompat.BAND_60GHZ,
|
||||||
val bit = features.takeLowestOneBit()
|
)) {
|
||||||
yield(WifiApManager.featureLookup(bit, true))
|
if (capability.getSupportedChannelList(band).isEmpty()) continue
|
||||||
features = features and bit.inv()
|
// reduce double reporting
|
||||||
|
features = features and flag.inv()
|
||||||
|
}
|
||||||
|
val result = parent.resources.getQuantityText(R.plurals.tethering_manage_wifi_capabilities, numClients ?: 0)
|
||||||
|
.format(locale, numClients ?: "?", maxClients, sequence {
|
||||||
|
if (WifiApManager.isApMacRandomizationSupported) yield(parent.getText(
|
||||||
|
R.string.tethering_manage_wifi_feature_ap_mac_randomization))
|
||||||
|
if (Services.wifi.isStaApConcurrencySupported) yield(parent.getText(
|
||||||
|
R.string.tethering_manage_wifi_feature_sta_ap_concurrency))
|
||||||
|
if (Build.VERSION.SDK_INT >= 31) {
|
||||||
|
if (Services.wifi.isBridgedApConcurrencySupported) yield(parent.getText(
|
||||||
|
R.string.tethering_manage_wifi_feature_bridged_ap_concurrency))
|
||||||
|
if (Services.wifi.isStaBridgedApConcurrencySupported) yield(parent.getText(
|
||||||
|
R.string.tethering_manage_wifi_feature_sta_bridged_ap_concurrency))
|
||||||
|
}
|
||||||
|
if (features != 0L) while (features != 0L) {
|
||||||
|
val bit = features.takeLowestOneBit()
|
||||||
|
yield(SoftApCapability.featureLookup(bit, true))
|
||||||
|
features = features and bit.inv()
|
||||||
|
}
|
||||||
|
}.joinToSpanned().let {
|
||||||
|
if (it.isEmpty()) parent.getText(R.string.tethering_manage_wifi_no_features) else it
|
||||||
|
})
|
||||||
|
if (Build.VERSION.SDK_INT >= 31) {
|
||||||
|
val list = SoftApConfigurationCompat.BAND_TYPES.map { band ->
|
||||||
|
val channels = capability.getSupportedChannelList(band)
|
||||||
|
if (channels.isNotEmpty()) StringBuilder().apply {
|
||||||
|
append(SoftApConfigurationCompat.bandLookup(band, true))
|
||||||
|
append(" (")
|
||||||
|
channels.sort()
|
||||||
|
var pending: Int? = null
|
||||||
|
var last = channels[0]
|
||||||
|
append(last)
|
||||||
|
for (channel in channels.asSequence().drop(1)) {
|
||||||
|
if (channel == last + 1) pending = channel else {
|
||||||
|
pending?.let {
|
||||||
|
append('-')
|
||||||
|
append(it)
|
||||||
|
pending = null
|
||||||
|
}
|
||||||
|
append(',')
|
||||||
|
append(channel)
|
||||||
|
}
|
||||||
|
last = channel
|
||||||
}
|
}
|
||||||
}.joinToString())
|
pending?.let {
|
||||||
}).joinToString("\n")
|
append('-')
|
||||||
|
append(it)
|
||||||
|
}
|
||||||
|
append(')')
|
||||||
|
} else null
|
||||||
|
}.filterNotNull()
|
||||||
|
if (list.isNotEmpty()) result.append(parent.getText(R.string.tethering_manage_wifi_supported_channels)
|
||||||
|
.format(locale, list.joinToString("; ")))
|
||||||
|
}
|
||||||
|
result
|
||||||
|
} ?: numClients?.let { numClients ->
|
||||||
|
app.resources.getQuantityText(R.plurals.tethering_manage_wifi_clients, numClients).format(locale,
|
||||||
|
numClients)
|
||||||
|
}
|
||||||
|
override val text get() = parent.resources.configuration.locale.let { locale ->
|
||||||
|
listOfNotNull(failureReason?.let { WifiApManager.failureReasonLookup(it) }, baseError, info.run {
|
||||||
|
if (isEmpty()) null else joinToSpanned("\n") @TargetApi(30) { parcel ->
|
||||||
|
val info = SoftApInfo(parcel)
|
||||||
|
val frequency = info.frequency
|
||||||
|
val channel = SoftApConfigurationCompat.frequencyToChannel(frequency)
|
||||||
|
val bandwidth = SoftApInfo.channelWidthLookup(info.bandwidth, true)
|
||||||
|
if (Build.VERSION.SDK_INT >= 31) {
|
||||||
|
val bssid = info.bssid.let { if (it == null) null else makeMacSpan(it.toString()) }
|
||||||
|
val bssidAp = info.apInstanceIdentifier?.let {
|
||||||
|
when (bssid) {
|
||||||
|
null -> it
|
||||||
|
is String -> "$bssid%$it" // take the fast route if possible
|
||||||
|
else -> SpannableStringBuilder(bssid).append("%$it")
|
||||||
|
}
|
||||||
|
} ?: bssid ?: "?"
|
||||||
|
val timeout = info.autoShutdownTimeoutMillis
|
||||||
|
parent.getText(if (timeout == 0L) {
|
||||||
|
R.string.tethering_manage_wifi_info_timeout_disabled
|
||||||
|
} else R.string.tethering_manage_wifi_info_timeout_enabled).format(locale,
|
||||||
|
frequency, channel, bandwidth, bssidAp, info.wifiStandard,
|
||||||
|
// http://unicode.org/cldr/trac/ticket/3407
|
||||||
|
DateUtils.formatElapsedTime(timeout / 1000))
|
||||||
|
} else parent.getText(R.string.tethering_manage_wifi_info).format(locale,
|
||||||
|
frequency, channel, bandwidth)
|
||||||
|
}
|
||||||
|
}, formatCapability(locale)).joinToSpanned("\n")
|
||||||
|
}
|
||||||
|
|
||||||
override fun start() = TetheringManager.startTethering(TetheringManager.TETHERING_WIFI, true, this)
|
override fun start() = TetheringManager.startTethering(TetheringManager.TETHERING_WIFI, true, this)
|
||||||
override fun stop() = TetheringManager.stopTethering(TetheringManager.TETHERING_WIFI, this::onException)
|
override fun stop() = TetheringManager.stopTethering(TetheringManager.TETHERING_WIFI, this::onException)
|
||||||
@@ -220,25 +307,24 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(),
|
|||||||
override fun stop() = TetheringManager.stopTethering(TetheringManager.TETHERING_USB, this::onException)
|
override fun stop() = TetheringManager.stopTethering(TetheringManager.TETHERING_USB, this::onException)
|
||||||
}
|
}
|
||||||
@RequiresApi(24)
|
@RequiresApi(24)
|
||||||
class Bluetooth(parent: TetheringFragment) : TetherManager(parent), DefaultLifecycleObserver {
|
class Bluetooth(parent: TetheringFragment, adapter: BluetoothAdapter) :
|
||||||
companion object {
|
TetherManager(parent), DefaultLifecycleObserver {
|
||||||
// TODO: migrate to framework Manifest.permission when stable
|
private val tethering = BluetoothTethering(parent.requireContext(), adapter) { data.notifyChange() }
|
||||||
private const val BLUETOOTH_CONNECT = "android.permission.BLUETOOTH_CONNECT"
|
|
||||||
}
|
|
||||||
|
|
||||||
private val tethering = BluetoothTethering(parent.requireContext()) { data.notifyChange() }
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
parent.viewLifecycleOwner.lifecycle.addObserver(this)
|
parent.viewLifecycleOwner.lifecycle.addObserver(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ensureInit(context: Context) = tethering.ensureInit(context)
|
fun ensureInit(context: Context) {
|
||||||
|
tethering.ensureInit(context)
|
||||||
|
onTetheringStarted() // force flush
|
||||||
|
}
|
||||||
override fun onResume(owner: LifecycleOwner) {
|
override fun onResume(owner: LifecycleOwner) {
|
||||||
if (!BuildCompat.isAtLeastS() || parent.requireContext().checkSelfPermission(BLUETOOTH_CONNECT) ==
|
if (Build.VERSION.SDK_INT < 31 || parent.requireContext().checkSelfPermission(
|
||||||
PackageManager.PERMISSION_GRANTED) {
|
Manifest.permission.BLUETOOTH_CONNECT) == PackageManager.PERMISSION_GRANTED) {
|
||||||
ensureInit(parent.requireContext())
|
tethering.ensureInit(parent.requireContext())
|
||||||
} else if (parent.shouldShowRequestPermissionRationale(BLUETOOTH_CONNECT)) {
|
} else if (parent.shouldShowRequestPermissionRationale(Manifest.permission.BLUETOOTH_CONNECT)) {
|
||||||
parent.requestBluetooth.launch(BLUETOOTH_CONNECT)
|
parent.requestBluetooth.launch(Manifest.permission.BLUETOOTH_CONNECT)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
override fun onDestroy(owner: LifecycleOwner) = tethering.close()
|
override fun onDestroy(owner: LifecycleOwner) = tethering.close()
|
||||||
@@ -246,17 +332,17 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(),
|
|||||||
override val title get() = parent.getString(R.string.tethering_manage_bluetooth)
|
override val title get() = parent.getString(R.string.tethering_manage_bluetooth)
|
||||||
override val tetherType get() = TetherType.BLUETOOTH
|
override val tetherType get() = TetherType.BLUETOOTH
|
||||||
override val type get() = VIEW_TYPE_BLUETOOTH
|
override val type get() = VIEW_TYPE_BLUETOOTH
|
||||||
override val isStarted get() = tethering.active == true
|
override val isStarted get() = tethering.active
|
||||||
override val text get() = listOfNotNull(
|
override val text get() = listOfNotNull(
|
||||||
if (tethering.active == null) tethering.activeFailureCause?.readableMessage else null,
|
if (tethering.active == null) tethering.activeFailureCause?.readableMessage else null,
|
||||||
baseError).joinToString("\n")
|
baseError).joinToString("\n")
|
||||||
|
|
||||||
override fun start() = BluetoothTethering.start(this)
|
override fun start() = tethering.start(this)
|
||||||
override fun stop() {
|
override fun stop() {
|
||||||
TetheringManager.stopTethering(TetheringManager.TETHERING_BLUETOOTH, this::onException)
|
tethering.stop(this::onException)
|
||||||
Thread.sleep(1) // give others a room to breathe
|
|
||||||
onTetheringStarted() // force flush state
|
onTetheringStarted() // force flush state
|
||||||
}
|
}
|
||||||
|
override fun onClickNull() = ManageBar.start(parent.requireContext())
|
||||||
}
|
}
|
||||||
@RequiresApi(30)
|
@RequiresApi(30)
|
||||||
class Ethernet(parent: TetheringFragment) : TetherManager(parent) {
|
class Ethernet(parent: TetheringFragment) : TetherManager(parent) {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
package be.mygod.vpnhotspot.manage
|
package be.mygod.vpnhotspot.manage
|
||||||
|
|
||||||
import android.annotation.TargetApi
|
import android.annotation.TargetApi
|
||||||
|
import android.bluetooth.BluetoothManager
|
||||||
import android.content.*
|
import android.content.*
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
@@ -15,6 +16,7 @@ import androidx.activity.result.contract.ActivityResultContracts
|
|||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.appcompat.widget.Toolbar
|
import androidx.appcompat.widget.Toolbar
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.content.getSystemService
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.DefaultItemAnimator
|
import androidx.recyclerview.widget.DefaultItemAnimator
|
||||||
@@ -34,6 +36,7 @@ import be.mygod.vpnhotspot.root.RootManager
|
|||||||
import be.mygod.vpnhotspot.root.WifiApCommands
|
import be.mygod.vpnhotspot.root.WifiApCommands
|
||||||
import be.mygod.vpnhotspot.util.*
|
import be.mygod.vpnhotspot.util.*
|
||||||
import be.mygod.vpnhotspot.widget.SmartSnackbar
|
import be.mygod.vpnhotspot.widget.SmartSnackbar
|
||||||
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.CompletableDeferred
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
@@ -42,16 +45,24 @@ import java.net.NetworkInterface
|
|||||||
import java.net.SocketException
|
import java.net.SocketException
|
||||||
|
|
||||||
class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClickListener {
|
class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClickListener {
|
||||||
inner class ManagerAdapter : ListAdapter<Manager, RecyclerView.ViewHolder>(Manager) {
|
inner class ManagerAdapter : ListAdapter<Manager, RecyclerView.ViewHolder>(Manager),
|
||||||
|
TetheringManager.TetheringEventCallback {
|
||||||
internal val repeaterManager by lazy { RepeaterManager(this@TetheringFragment) }
|
internal val repeaterManager by lazy { RepeaterManager(this@TetheringFragment) }
|
||||||
@get:RequiresApi(26)
|
@get:RequiresApi(26)
|
||||||
internal val localOnlyHotspotManager by lazy @TargetApi(26) { LocalOnlyHotspotManager(this@TetheringFragment) }
|
internal val localOnlyHotspotManager by lazy @TargetApi(26) { LocalOnlyHotspotManager(this@TetheringFragment) }
|
||||||
internal val bluetoothManager by lazy @TargetApi(24) { TetherManager.Bluetooth(this@TetheringFragment) }
|
@get:RequiresApi(24)
|
||||||
|
internal val bluetoothManager by lazy {
|
||||||
|
if (Build.VERSION.SDK_INT >= 24) requireContext().getSystemService<BluetoothManager>()?.adapter?.let {
|
||||||
|
TetherManager.Bluetooth(this@TetheringFragment, it)
|
||||||
|
} else null
|
||||||
|
}
|
||||||
@get:RequiresApi(24)
|
@get:RequiresApi(24)
|
||||||
private val tetherManagers by lazy @TargetApi(24) {
|
private val tetherManagers by lazy @TargetApi(24) {
|
||||||
listOf(TetherManager.Wifi(this@TetheringFragment),
|
listOfNotNull(
|
||||||
TetherManager.Usb(this@TetheringFragment),
|
TetherManager.Wifi(this@TetheringFragment),
|
||||||
bluetoothManager)
|
TetherManager.Usb(this@TetheringFragment),
|
||||||
|
bluetoothManager,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
@get:RequiresApi(30)
|
@get:RequiresApi(30)
|
||||||
private val tetherManagers30 by lazy @TargetApi(30) {
|
private val tetherManagers30 by lazy @TargetApi(30) {
|
||||||
@@ -59,30 +70,32 @@ class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClick
|
|||||||
TetherManager.Ncm(this@TetheringFragment),
|
TetherManager.Ncm(this@TetheringFragment),
|
||||||
TetherManager.WiGig(this@TetheringFragment))
|
TetherManager.WiGig(this@TetheringFragment))
|
||||||
}
|
}
|
||||||
private val wifiManagerLegacy by lazy @Suppress("Deprecation") {
|
private val wifiManagerLegacy by lazy { TetherManager.WifiLegacy(this@TetheringFragment) }
|
||||||
TetherManager.WifiLegacy(this@TetheringFragment)
|
|
||||||
}
|
|
||||||
|
|
||||||
private var enabledIfaces = emptyList<String>()
|
var activeIfaces = emptyList<String>()
|
||||||
|
var localOnlyIfaces = emptyList<String>()
|
||||||
|
var erroredIfaces = emptyList<String>()
|
||||||
private var listDeferred = CompletableDeferred<List<Manager>>(emptyList())
|
private var listDeferred = CompletableDeferred<List<Manager>>(emptyList())
|
||||||
private fun updateEnabledTypes() {
|
fun updateEnabledTypes() {
|
||||||
this@TetheringFragment.enabledTypes = enabledIfaces.map { TetherType.ofInterface(it) }.toSet()
|
this@TetheringFragment.enabledTypes =
|
||||||
|
(activeIfaces + localOnlyIfaces).map { TetherType.ofInterface(it) }.toSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun notifyInterfaceChanged(lastList: List<Manager>? = null) {
|
val lastErrors = mutableMapOf<String, Int>()
|
||||||
@Suppress("NAME_SHADOWING") val lastList = lastList ?: listDeferred.await()
|
override fun onError(ifName: String, error: Int) {
|
||||||
val first = lastList.indexOfFirst { it is InterfaceManager }
|
if (error == 0) lastErrors.remove(ifName) else lastErrors[ifName] = error
|
||||||
if (first >= 0) notifyItemRangeChanged(first, lastList.indexOfLast { it is InterfaceManager } - first + 1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun notifyTetherTypeChanged() {
|
suspend fun notifyTetherTypeChanged() {
|
||||||
updateEnabledTypes()
|
updateEnabledTypes()
|
||||||
val lastList = listDeferred.await()
|
val lastList = listDeferred.await()
|
||||||
notifyInterfaceChanged(lastList)
|
var first = lastList.indexOfFirst { it is InterfaceManager }
|
||||||
val first = lastList.indexOfLast { it !is TetherManager } + 1
|
if (first >= 0) notifyItemRangeChanged(first, lastList.indexOfLast { it is InterfaceManager } - first + 1)
|
||||||
|
first = lastList.indexOfLast { it !is TetherManager } + 1
|
||||||
notifyItemRangeChanged(first, lastList.size - first)
|
notifyItemRangeChanged(first, lastList.size - first)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun update(activeIfaces: List<String>, localOnlyIfaces: List<String>, erroredIfaces: List<String>) {
|
fun update() {
|
||||||
val deferred = CompletableDeferred<List<Manager>>()
|
val deferred = CompletableDeferred<List<Manager>>()
|
||||||
listDeferred = deferred
|
listDeferred = deferred
|
||||||
ifaceLookup = try {
|
ifaceLookup = try {
|
||||||
@@ -91,8 +104,6 @@ class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClick
|
|||||||
Timber.d(e)
|
Timber.d(e)
|
||||||
emptyMap()
|
emptyMap()
|
||||||
}
|
}
|
||||||
enabledIfaces = activeIfaces + localOnlyIfaces
|
|
||||||
updateEnabledTypes()
|
|
||||||
|
|
||||||
val list = ArrayList<Manager>()
|
val list = ArrayList<Manager>()
|
||||||
if (Services.p2p != null) list.add(repeaterManager)
|
if (Services.p2p != null) list.add(repeaterManager)
|
||||||
@@ -104,11 +115,11 @@ class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClick
|
|||||||
list.add(ManageBar)
|
list.add(ManageBar)
|
||||||
if (Build.VERSION.SDK_INT >= 24) {
|
if (Build.VERSION.SDK_INT >= 24) {
|
||||||
list.addAll(tetherManagers)
|
list.addAll(tetherManagers)
|
||||||
tetherManagers.forEach { it.updateErrorMessage(erroredIfaces) }
|
tetherManagers.forEach { it.updateErrorMessage(erroredIfaces, lastErrors) }
|
||||||
}
|
}
|
||||||
if (Build.VERSION.SDK_INT >= 30) {
|
if (Build.VERSION.SDK_INT >= 30) {
|
||||||
list.addAll(tetherManagers30)
|
list.addAll(tetherManagers30)
|
||||||
tetherManagers30.forEach { it.updateErrorMessage(erroredIfaces) }
|
tetherManagers30.forEach { it.updateErrorMessage(erroredIfaces, lastErrors) }
|
||||||
}
|
}
|
||||||
if (Build.VERSION.SDK_INT < 26) {
|
if (Build.VERSION.SDK_INT < 26) {
|
||||||
list.add(wifiManagerLegacy)
|
list.add(wifiManagerLegacy)
|
||||||
@@ -125,7 +136,10 @@ class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClick
|
|||||||
|
|
||||||
@RequiresApi(29)
|
@RequiresApi(29)
|
||||||
val startRepeater = registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
|
val startRepeater = registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
|
||||||
if (granted) requireActivity().startForegroundService(Intent(activity, RepeaterService::class.java))
|
if (granted) requireActivity().startForegroundService(Intent(activity, RepeaterService::class.java)) else {
|
||||||
|
Snackbar.make((activity as MainActivity).binding.fragmentHolder,
|
||||||
|
R.string.repeater_missing_location_permissions, Snackbar.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@RequiresApi(26)
|
@RequiresApi(26)
|
||||||
val startLocalOnlyHotspot = registerForActivityResult(ActivityResultContracts.RequestPermission()) {
|
val startLocalOnlyHotspot = registerForActivityResult(ActivityResultContracts.RequestPermission()) {
|
||||||
@@ -133,7 +147,7 @@ class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClick
|
|||||||
}
|
}
|
||||||
@RequiresApi(31)
|
@RequiresApi(31)
|
||||||
val requestBluetooth = registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
|
val requestBluetooth = registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
|
||||||
if (granted) adapter.bluetoothManager.ensureInit(requireContext())
|
if (granted) adapter.bluetoothManager!!.ensureInit(requireContext())
|
||||||
}
|
}
|
||||||
|
|
||||||
var ifaceLookup: Map<String, NetworkInterface> = emptyMap()
|
var ifaceLookup: Map<String, NetworkInterface> = emptyMap()
|
||||||
@@ -142,9 +156,12 @@ class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClick
|
|||||||
var binder: TetheringService.Binder? = null
|
var binder: TetheringService.Binder? = null
|
||||||
private val adapter = ManagerAdapter()
|
private val adapter = ManagerAdapter()
|
||||||
private val receiver = broadcastReceiver { _, intent ->
|
private val receiver = broadcastReceiver { _, intent ->
|
||||||
adapter.update(intent.tetheredIfaces ?: return@broadcastReceiver,
|
adapter.activeIfaces = intent.tetheredIfaces ?: return@broadcastReceiver
|
||||||
intent.localOnlyTetheredIfaces ?: return@broadcastReceiver,
|
adapter.localOnlyIfaces = intent.localOnlyTetheredIfaces ?: return@broadcastReceiver
|
||||||
intent.getStringArrayListExtra(TetheringManager.EXTRA_ERRORED_TETHER) ?: return@broadcastReceiver)
|
adapter.erroredIfaces = intent.getStringArrayListExtra(TetheringManager.EXTRA_ERRORED_TETHER)
|
||||||
|
?: return@broadcastReceiver
|
||||||
|
adapter.updateEnabledTypes()
|
||||||
|
adapter.update()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateMonitorList(canMonitor: List<String> = emptyList()) {
|
private fun updateMonitorList(canMonitor: List<String> = emptyList()) {
|
||||||
@@ -219,7 +236,7 @@ class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClick
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||||
AlertDialogFragment.setResultListener<WifiApDialogFragment, WifiApDialogFragment.Arg>(this) { which, ret ->
|
AlertDialogFragment.setResultListener<WifiApDialogFragment, WifiApDialogFragment.Arg>(this) { which, ret ->
|
||||||
if (which == DialogInterface.BUTTON_POSITIVE) viewLifecycleOwner.lifecycleScope.launchWhenCreated {
|
if (which == DialogInterface.BUTTON_POSITIVE) viewLifecycleOwner.lifecycleScope.launchWhenCreated {
|
||||||
val configuration = ret!!.configuration
|
val configuration = ret!!.configuration
|
||||||
@@ -251,7 +268,7 @@ class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClick
|
|||||||
binding.interfaces.layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
|
binding.interfaces.layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
|
||||||
binding.interfaces.itemAnimator = DefaultItemAnimator()
|
binding.interfaces.itemAnimator = DefaultItemAnimator()
|
||||||
binding.interfaces.adapter = adapter
|
binding.interfaces.adapter = adapter
|
||||||
adapter.update(emptyList(), emptyList(), emptyList())
|
adapter.update()
|
||||||
ServiceForegroundConnector(this, this, TetheringService::class)
|
ServiceForegroundConnector(this, this, TetheringService::class)
|
||||||
(activity as MainActivity).binding.toolbar.apply {
|
(activity as MainActivity).binding.toolbar.apply {
|
||||||
inflateMenu(R.menu.toolbar_tethering)
|
inflateMenu(R.menu.toolbar_tethering)
|
||||||
@@ -275,19 +292,22 @@ class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClick
|
|||||||
|
|
||||||
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
||||||
binder = service as TetheringService.Binder
|
binder = service as TetheringService.Binder
|
||||||
service.routingsChanged[this] = {
|
service.routingsChanged[this] = { lifecycleScope.launchWhenStarted { adapter.update() } }
|
||||||
lifecycleScope.launchWhenStarted { adapter.notifyInterfaceChanged() }
|
|
||||||
}
|
|
||||||
requireContext().registerReceiver(receiver, IntentFilter(TetheringManager.ACTION_TETHER_STATE_CHANGED))
|
requireContext().registerReceiver(receiver, IntentFilter(TetheringManager.ACTION_TETHER_STATE_CHANGED))
|
||||||
if (Build.VERSION.SDK_INT >= 30) TetherType.listener[this] = {
|
if (Build.VERSION.SDK_INT >= 30) {
|
||||||
lifecycleScope.launchWhenStarted { adapter.notifyTetherTypeChanged() }
|
TetheringManager.registerTetheringEventCallback(null, adapter)
|
||||||
|
TetherType.listener[this] = { lifecycleScope.launchWhenStarted { adapter.notifyTetherTypeChanged() } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onServiceDisconnected(name: ComponentName?) {
|
override fun onServiceDisconnected(name: ComponentName?) {
|
||||||
(binder ?: return).routingsChanged -= this
|
(binder ?: return).routingsChanged -= this
|
||||||
binder = null
|
binder = null
|
||||||
if (Build.VERSION.SDK_INT >= 30) TetherType.listener -= this
|
if (Build.VERSION.SDK_INT >= 30) {
|
||||||
|
TetherType.listener -= this
|
||||||
|
TetheringManager.unregisterTetheringEventCallback(adapter)
|
||||||
|
adapter.lastErrors.clear()
|
||||||
|
}
|
||||||
requireContext().unregisterReceiver(receiver)
|
requireContext().unregisterReceiver(receiver)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package be.mygod.vpnhotspot.manage
|
package be.mygod.vpnhotspot.manage
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothManager
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
@@ -11,6 +12,7 @@ import android.service.quicksettings.Tile
|
|||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.content.getSystemService
|
||||||
import be.mygod.vpnhotspot.R
|
import be.mygod.vpnhotspot.R
|
||||||
import be.mygod.vpnhotspot.TetheringService
|
import be.mygod.vpnhotspot.TetheringService
|
||||||
import be.mygod.vpnhotspot.net.TetherType
|
import be.mygod.vpnhotspot.net.TetherType
|
||||||
@@ -151,15 +153,16 @@ sealed class TetheringTileService : IpNeighbourMonitoringTileService(), Tetherin
|
|||||||
override val labelString get() = R.string.tethering_manage_bluetooth
|
override val labelString get() = R.string.tethering_manage_bluetooth
|
||||||
override val tetherType get() = TetherType.BLUETOOTH
|
override val tetherType get() = TetherType.BLUETOOTH
|
||||||
|
|
||||||
override fun start() = BluetoothTethering.start(this)
|
override fun start() = tethering!!.start(this)
|
||||||
override fun stop() {
|
override fun stop() {
|
||||||
TetheringManager.stopTethering(TetheringManager.TETHERING_BLUETOOTH, this::onException)
|
tethering!!.stop(this::onException)
|
||||||
Thread.sleep(1) // give others a room to breathe
|
|
||||||
onTetheringStarted() // force flush state
|
onTetheringStarted() // force flush state
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStartListening() {
|
override fun onStartListening() {
|
||||||
tethering = BluetoothTethering(this) { updateTile() }
|
tethering = getSystemService<BluetoothManager>()?.adapter?.let {
|
||||||
|
BluetoothTethering(this, it) { updateTile() }
|
||||||
|
}
|
||||||
super.onStartListening()
|
super.onStartListening()
|
||||||
}
|
}
|
||||||
override fun onStopListening() {
|
override fun onStopListening() {
|
||||||
@@ -187,7 +190,7 @@ sealed class TetheringTileService : IpNeighbourMonitoringTileService(), Tetherin
|
|||||||
icon = tileOff
|
icon = tileOff
|
||||||
}
|
}
|
||||||
null -> {
|
null -> {
|
||||||
state = Tile.STATE_UNAVAILABLE
|
state = Tile.STATE_INACTIVE
|
||||||
icon = tileOff
|
icon = tileOff
|
||||||
subtitle(tethering?.activeFailureCause?.readableMessage)
|
subtitle(tethering?.activeFailureCause?.readableMessage)
|
||||||
}
|
}
|
||||||
@@ -198,7 +201,8 @@ sealed class TetheringTileService : IpNeighbourMonitoringTileService(), Tetherin
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onClick() {
|
override fun onClick() {
|
||||||
when (tethering?.active) {
|
val tethering = tethering
|
||||||
|
if (tethering == null) tapPending = true else when (tethering.active) {
|
||||||
true -> {
|
true -> {
|
||||||
val binder = binder
|
val binder = binder
|
||||||
if (binder == null) tapPending = true else {
|
if (binder == null) tapPending = true else {
|
||||||
@@ -212,7 +216,7 @@ sealed class TetheringTileService : IpNeighbourMonitoringTileService(), Tetherin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
false -> start()
|
false -> start()
|
||||||
else -> tapPending = true
|
else -> ManageBar.start(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,9 +71,9 @@ enum class TetherType(@DrawableRes val icon: Int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(30)
|
@RequiresApi(30)
|
||||||
override fun onTetherableInterfaceRegexpsChanged(args: Array<out Any?>?) = synchronized(this) {
|
override fun onTetherableInterfaceRegexpsChanged(reg: Any?) = synchronized(this) {
|
||||||
if (requiresUpdate) return@synchronized
|
if (requiresUpdate) return@synchronized
|
||||||
Timber.i("onTetherableInterfaceRegexpsChanged: ${args?.contentDeepToString()}")
|
Timber.i("onTetherableInterfaceRegexpsChanged: $reg")
|
||||||
TetheringManager.unregisterTetheringEventCallback(this)
|
TetheringManager.unregisterTetheringEventCallback(this)
|
||||||
requiresUpdate = true
|
requiresUpdate = true
|
||||||
listener()
|
listener()
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import android.content.Context
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.content.pm.ResolveInfo
|
|
||||||
import android.net.ConnectivityManager
|
import android.net.ConnectivityManager
|
||||||
import android.net.Network
|
import android.net.Network
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
@@ -67,7 +66,11 @@ object TetheringManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private object InPlaceExecutor : Executor {
|
private object InPlaceExecutor : Executor {
|
||||||
override fun execute(command: Runnable) = command.run()
|
override fun execute(command: Runnable) = try {
|
||||||
|
command.run()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.w(e) // prevent Binder stub swallowing the exception
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -135,7 +138,7 @@ object TetheringManager {
|
|||||||
* Requires MANAGE_USB permission, unfortunately.
|
* Requires MANAGE_USB permission, unfortunately.
|
||||||
*
|
*
|
||||||
* Source: https://android.googlesource.com/platform/frameworks/base/+/7ca5d3a/services/usb/java/com/android/server/usb/UsbService.java#389
|
* Source: https://android.googlesource.com/platform/frameworks/base/+/7ca5d3a/services/usb/java/com/android/server/usb/UsbService.java#389
|
||||||
* @see [startTethering].
|
* @see startTethering
|
||||||
*/
|
*/
|
||||||
@RequiresApi(24)
|
@RequiresApi(24)
|
||||||
const val TETHERING_USB = 1
|
const val TETHERING_USB = 1
|
||||||
@@ -143,14 +146,14 @@ object TetheringManager {
|
|||||||
* Bluetooth tethering type.
|
* Bluetooth tethering type.
|
||||||
*
|
*
|
||||||
* Requires BLUETOOTH permission.
|
* Requires BLUETOOTH permission.
|
||||||
* @see [startTethering].
|
* @see startTethering
|
||||||
*/
|
*/
|
||||||
@RequiresApi(24)
|
@RequiresApi(24)
|
||||||
const val TETHERING_BLUETOOTH = 2
|
const val TETHERING_BLUETOOTH = 2
|
||||||
/**
|
/**
|
||||||
* Ncm local tethering type.
|
* Ncm local tethering type.
|
||||||
*
|
*
|
||||||
* @see [startTethering]
|
* @see startTethering
|
||||||
*/
|
*/
|
||||||
@RequiresApi(30)
|
@RequiresApi(30)
|
||||||
const val TETHERING_NCM = 4
|
const val TETHERING_NCM = 4
|
||||||
@@ -158,7 +161,7 @@ object TetheringManager {
|
|||||||
* Ethernet tethering type.
|
* Ethernet tethering type.
|
||||||
*
|
*
|
||||||
* Requires MANAGE_USB permission, also.
|
* Requires MANAGE_USB permission, also.
|
||||||
* @see [startTethering]
|
* @see startTethering
|
||||||
*/
|
*/
|
||||||
@RequiresApi(30)
|
@RequiresApi(30)
|
||||||
const val TETHERING_ETHERNET = 5
|
const val TETHERING_ETHERNET = 5
|
||||||
@@ -248,13 +251,12 @@ object TetheringManager {
|
|||||||
val proxy = ProxyBuilder.forClass(classOnStartTetheringCallback).apply {
|
val proxy = ProxyBuilder.forClass(classOnStartTetheringCallback).apply {
|
||||||
dexCache(cacheDir)
|
dexCache(cacheDir)
|
||||||
handler { proxy, method, args ->
|
handler { proxy, method, args ->
|
||||||
if (args.isNotEmpty()) Timber.w("Unexpected args for ${method.name}: $args")
|
|
||||||
@Suppress("NAME_SHADOWING") val callback = reference.get()
|
@Suppress("NAME_SHADOWING") val callback = reference.get()
|
||||||
when (method.name) {
|
if (args.isEmpty()) when (method.name) {
|
||||||
"onTetheringStarted" -> callback?.onTetheringStarted()
|
"onTetheringStarted" -> return@handler callback?.onTetheringStarted()
|
||||||
"onTetheringFailed" -> callback?.onTetheringFailed()
|
"onTetheringFailed" -> return@handler callback?.onTetheringFailed()
|
||||||
else -> ProxyBuilder.callSuper(proxy, method, args)
|
|
||||||
}
|
}
|
||||||
|
ProxyBuilder.callSuper(proxy, method, args)
|
||||||
}
|
}
|
||||||
}.build()
|
}.build()
|
||||||
startTetheringLegacy(Services.connectivity, type, showProvisioningUi, proxy, handler)
|
startTetheringLegacy(Services.connectivity, type, showProvisioningUi, proxy, handler)
|
||||||
@@ -276,13 +278,9 @@ object TetheringManager {
|
|||||||
arrayOf(interfaceStartTetheringCallback), object : InvocationHandler {
|
arrayOf(interfaceStartTetheringCallback), object : InvocationHandler {
|
||||||
override fun invoke(proxy: Any, method: Method, args: Array<out Any?>?): Any? {
|
override fun invoke(proxy: Any, method: Method, args: Array<out Any?>?): Any? {
|
||||||
@Suppress("NAME_SHADOWING") val callback = reference.get()
|
@Suppress("NAME_SHADOWING") val callback = reference.get()
|
||||||
return when (val name = method.name) {
|
return when {
|
||||||
"onTetheringStarted" -> {
|
method.matches("onTetheringStarted") -> callback?.onTetheringStarted()
|
||||||
if (!args.isNullOrEmpty()) Timber.w("Unexpected args for $name: $args")
|
method.matches("onTetheringFailed", Integer.TYPE) -> {
|
||||||
callback?.onTetheringStarted()
|
|
||||||
}
|
|
||||||
"onTetheringFailed" -> {
|
|
||||||
if (args?.size != 1) Timber.w("Unexpected args for $name: $args")
|
|
||||||
callback?.onTetheringFailed(args?.get(0) as Int)
|
callback?.onTetheringFailed(args?.get(0) as Int)
|
||||||
}
|
}
|
||||||
else -> callSuper(interfaceStartTetheringCallback, proxy, method, args)
|
else -> callSuper(interfaceStartTetheringCallback, proxy, method, args)
|
||||||
@@ -446,7 +444,7 @@ object TetheringManager {
|
|||||||
* *@param reg The new regular expressions.
|
* *@param reg The new regular expressions.
|
||||||
* @hide
|
* @hide
|
||||||
*/
|
*/
|
||||||
fun onTetherableInterfaceRegexpsChanged(args: Array<out Any?>?) {}
|
fun onTetherableInterfaceRegexpsChanged(reg: Any?) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when there was a change in the list of tetherable interfaces. Tetherable
|
* Called when there was a change in the list of tetherable interfaces. Tetherable
|
||||||
@@ -542,40 +540,34 @@ object TetheringManager {
|
|||||||
override fun invoke(proxy: Any, method: Method, args: Array<out Any?>?): Any? {
|
override fun invoke(proxy: Any, method: Method, args: Array<out Any?>?): Any? {
|
||||||
@Suppress("NAME_SHADOWING")
|
@Suppress("NAME_SHADOWING")
|
||||||
val callback = reference.get()
|
val callback = reference.get()
|
||||||
val noArgs = args?.size ?: 0
|
return when {
|
||||||
return when (val name = method.name) {
|
method.matches("onTetheringSupported", Boolean::class.java) -> {
|
||||||
"onTetheringSupported" -> {
|
|
||||||
if (noArgs != 1) Timber.w("Unexpected args for $name: $args")
|
|
||||||
callback?.onTetheringSupported(args!![0] as Boolean)
|
callback?.onTetheringSupported(args!![0] as Boolean)
|
||||||
}
|
}
|
||||||
"onUpstreamChanged" -> {
|
method.matches1<Network>("onUpstreamChanged") -> {
|
||||||
if (noArgs != 1) Timber.w("Unexpected args for $name: $args")
|
|
||||||
callback?.onUpstreamChanged(args!![0] as Network?)
|
callback?.onUpstreamChanged(args!![0] as Network?)
|
||||||
}
|
}
|
||||||
"onTetherableInterfaceRegexpsChanged" -> {
|
method.name == "onTetherableInterfaceRegexpsChanged" &&
|
||||||
if (regexpsSent) callback?.onTetherableInterfaceRegexpsChanged(args)
|
method.parameters.singleOrNull()?.type?.name ==
|
||||||
|
"android.net.TetheringManager\$TetheringInterfaceRegexps" -> {
|
||||||
|
if (regexpsSent) callback?.onTetherableInterfaceRegexpsChanged(args!!.single())
|
||||||
regexpsSent = true
|
regexpsSent = true
|
||||||
}
|
}
|
||||||
"onTetherableInterfacesChanged" -> {
|
method.matches1<java.util.List<*>>("onTetherableInterfacesChanged") -> {
|
||||||
if (noArgs != 1) Timber.w("Unexpected args for $name: $args")
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
callback?.onTetherableInterfacesChanged(args!![0] as List<String?>)
|
callback?.onTetherableInterfacesChanged(args!![0] as List<String?>)
|
||||||
}
|
}
|
||||||
"onTetheredInterfacesChanged" -> {
|
method.matches1<java.util.List<*>>("onTetheredInterfacesChanged") -> {
|
||||||
if (noArgs != 1) Timber.w("Unexpected args for $name: $args")
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
callback?.onTetheredInterfacesChanged(args!![0] as List<String?>)
|
callback?.onTetheredInterfacesChanged(args!![0] as List<String?>)
|
||||||
}
|
}
|
||||||
"onError" -> {
|
method.matches("onError", String::class.java, Integer.TYPE) -> {
|
||||||
if (noArgs != 2) Timber.w("Unexpected args for $name: $args")
|
|
||||||
callback?.onError(args!![0] as String, args[1] as Int)
|
callback?.onError(args!![0] as String, args[1] as Int)
|
||||||
}
|
}
|
||||||
"onClientsChanged" -> {
|
method.matches1<java.util.Collection<*>>("onClientsChanged") -> {
|
||||||
if (noArgs != 1) Timber.w("Unexpected args for $name: $args")
|
|
||||||
callback?.onClientsChanged(args!![0] as Collection<*>)
|
callback?.onClientsChanged(args!![0] as Collection<*>)
|
||||||
}
|
}
|
||||||
"onOffloadStatusChanged" -> {
|
method.matches("onOffloadStatusChanged", Integer.TYPE) -> {
|
||||||
if (noArgs != 1) Timber.w("Unexpected args for $name: $args")
|
|
||||||
callback?.onOffloadStatusChanged(args!![0] as Int)
|
callback?.onOffloadStatusChanged(args!![0] as Int)
|
||||||
}
|
}
|
||||||
else -> callSuper(interfaceTetheringEventCallback, proxy, method, args)
|
else -> callSuper(interfaceTetheringEventCallback, proxy, method, args)
|
||||||
@@ -629,6 +621,7 @@ object TetheringManager {
|
|||||||
* @return error The error code of the last error tethering or untethering the named
|
* @return error The error code of the last error tethering or untethering the named
|
||||||
* interface
|
* interface
|
||||||
*/
|
*/
|
||||||
|
@Deprecated("Use {@link TetheringEventCallback#onError(String, int)} instead.")
|
||||||
fun getLastTetherError(iface: String): Int = getLastTetherError(Services.connectivity, iface) as Int
|
fun getLastTetherError(iface: String): Int = getLastTetherError(Services.connectivity, iface) as Int
|
||||||
|
|
||||||
val tetherErrorLookup = ConstantLookup("TETHER_ERROR_",
|
val tetherErrorLookup = ConstantLookup("TETHER_ERROR_",
|
||||||
|
|||||||
@@ -6,7 +6,10 @@ import android.net.LinkProperties
|
|||||||
import android.net.Network
|
import android.net.Network
|
||||||
import android.net.NetworkCapabilities
|
import android.net.NetworkCapabilities
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
import be.mygod.vpnhotspot.util.Services
|
import be.mygod.vpnhotspot.util.Services
|
||||||
|
import be.mygod.vpnhotspot.util.globalNetworkRequestBuilder
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@@ -18,10 +21,10 @@ object DefaultNetworkMonitor : UpstreamMonitor() {
|
|||||||
* Unfortunately registerDefaultNetworkCallback is going to return VPN interface since Android P DP1:
|
* Unfortunately registerDefaultNetworkCallback is going to return VPN interface since Android P DP1:
|
||||||
* https://android.googlesource.com/platform/frameworks/base/+/dda156ab0c5d66ad82bdcf76cda07cbc0a9c8a2e
|
* https://android.googlesource.com/platform/frameworks/base/+/dda156ab0c5d66ad82bdcf76cda07cbc0a9c8a2e
|
||||||
*/
|
*/
|
||||||
private val networkRequest = networkRequestBuilder()
|
private val networkRequest = globalNetworkRequestBuilder().apply {
|
||||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
||||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
|
addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
|
||||||
.build()
|
}.build()
|
||||||
private val networkCallback = object : ConnectivityManager.NetworkCallback() {
|
private val networkCallback = object : ConnectivityManager.NetworkCallback() {
|
||||||
override fun onAvailable(network: Network) {
|
override fun onAvailable(network: Network) {
|
||||||
val properties = Services.connectivity.getLinkProperties(network)
|
val properties = Services.connectivity.getLinkProperties(network)
|
||||||
@@ -51,15 +54,22 @@ object DefaultNetworkMonitor : UpstreamMonitor() {
|
|||||||
callback.onAvailable(currentLinkProperties)
|
callback.onAvailable(currentLinkProperties)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (Build.VERSION.SDK_INT in 24..27) @TargetApi(24) {
|
when (Build.VERSION.SDK_INT) {
|
||||||
Services.connectivity.registerDefaultNetworkCallback(networkCallback)
|
in 31..Int.MAX_VALUE -> @TargetApi(31) {
|
||||||
} else try {
|
Services.connectivity.registerBestMatchingNetworkCallback(networkRequest, networkCallback,
|
||||||
Services.connectivity.requestNetwork(networkRequest, networkCallback)
|
Handler(Looper.getMainLooper()))
|
||||||
} catch (e: RuntimeException) {
|
}
|
||||||
// SecurityException would be thrown in requestNetwork on Android 6.0 thanks to Google's stupid bug
|
in 24..27 -> @TargetApi(24) {
|
||||||
if (Build.VERSION.SDK_INT != 23) throw e
|
Services.connectivity.registerDefaultNetworkCallback(networkCallback)
|
||||||
GlobalScope.launch { callback.onFallback() }
|
}
|
||||||
return
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
registered = true
|
registered = true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import android.net.Network
|
|||||||
import android.net.NetworkCapabilities
|
import android.net.NetworkCapabilities
|
||||||
import be.mygod.vpnhotspot.util.Services
|
import be.mygod.vpnhotspot.util.Services
|
||||||
import be.mygod.vpnhotspot.util.allInterfaceNames
|
import be.mygod.vpnhotspot.util.allInterfaceNames
|
||||||
|
import be.mygod.vpnhotspot.util.globalNetworkRequestBuilder
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
@@ -18,7 +19,7 @@ class InterfaceMonitor(private val ifaceRegex: String) : UpstreamMonitor() {
|
|||||||
Timber.d(e);
|
Timber.d(e);
|
||||||
{ it == ifaceRegex }
|
{ it == ifaceRegex }
|
||||||
}
|
}
|
||||||
private val request = networkRequestBuilder().apply {
|
private val request = globalNetworkRequestBuilder().apply {
|
||||||
removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
|
removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
|
||||||
removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
|
removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
|
||||||
removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
|
removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ class TetherTimeoutMonitor(private val timeout: Long = 0,
|
|||||||
val info = WifiApManager.resolvedActivity.activityInfo
|
val info = WifiApManager.resolvedActivity.activityInfo
|
||||||
val resources = app.packageManager.getResourcesForApplication(info.applicationInfo)
|
val resources = app.packageManager.getResourcesForApplication(info.applicationInfo)
|
||||||
resources.getInteger(resources.findIdentifier("config_wifiFrameworkSoftApShutDownTimeoutMilliseconds",
|
resources.getInteger(resources.findIdentifier("config_wifiFrameworkSoftApShutDownTimeoutMilliseconds",
|
||||||
"integer", "com.android.wifi.resources", info.packageName))
|
"integer", WifiApManager.RESOURCES_PACKAGE, info.packageName))
|
||||||
}
|
}
|
||||||
} catch (e: RuntimeException) {
|
} catch (e: RuntimeException) {
|
||||||
Timber.w(e)
|
Timber.w(e)
|
||||||
|
|||||||
@@ -2,9 +2,6 @@ package be.mygod.vpnhotspot.net.monitor
|
|||||||
|
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.net.LinkProperties
|
import android.net.LinkProperties
|
||||||
import android.net.NetworkCapabilities
|
|
||||||
import android.net.NetworkRequest
|
|
||||||
import android.os.Build
|
|
||||||
import be.mygod.vpnhotspot.App.Companion.app
|
import be.mygod.vpnhotspot.App.Companion.app
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -23,13 +20,6 @@ abstract class UpstreamMonitor {
|
|||||||
}
|
}
|
||||||
private var monitor = generateMonitor()
|
private var monitor = generateMonitor()
|
||||||
|
|
||||||
fun networkRequestBuilder() = NetworkRequest.Builder().apply {
|
|
||||||
if (Build.VERSION.SDK_INT == 23) { // workarounds for OEM bugs
|
|
||||||
removeCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
|
|
||||||
removeCapability(NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun registerCallback(callback: Callback) = synchronized(this) { monitor.registerCallback(callback) }
|
fun registerCallback(callback: Callback) = synchronized(this) { monitor.registerCallback(callback) }
|
||||||
fun unregisterCallback(callback: Callback) = synchronized(this) { monitor.unregisterCallback(callback) }
|
fun unregisterCallback(callback: Callback) = synchronized(this) { monitor.unregisterCallback(callback) }
|
||||||
|
|
||||||
|
|||||||
@@ -5,15 +5,16 @@ import android.net.LinkProperties
|
|||||||
import android.net.Network
|
import android.net.Network
|
||||||
import android.net.NetworkCapabilities
|
import android.net.NetworkCapabilities
|
||||||
import be.mygod.vpnhotspot.util.Services
|
import be.mygod.vpnhotspot.util.Services
|
||||||
|
import be.mygod.vpnhotspot.util.globalNetworkRequestBuilder
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
object VpnMonitor : UpstreamMonitor() {
|
object VpnMonitor : UpstreamMonitor() {
|
||||||
private val request = networkRequestBuilder()
|
private val request = globalNetworkRequestBuilder().apply {
|
||||||
.addTransportType(NetworkCapabilities.TRANSPORT_VPN)
|
addTransportType(NetworkCapabilities.TRANSPORT_VPN)
|
||||||
.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
|
removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
|
||||||
.build()
|
}.build()
|
||||||
private var registered = false
|
private var registered = false
|
||||||
|
|
||||||
private val available = HashMap<Network, LinkProperties?>()
|
private val available = HashMap<Network, LinkProperties?>()
|
||||||
|
|||||||
@@ -75,7 +75,13 @@ class P2pSupplicantConfiguration(private val group: WifiP2pGroup? = null) {
|
|||||||
if (matchedBssid.isEmpty()) {
|
if (matchedBssid.isEmpty()) {
|
||||||
check(block.pskLine == null && block.psk == null)
|
check(block.pskLine == null && block.psk == null)
|
||||||
if (match.groups[5] != null) {
|
if (match.groups[5] != null) {
|
||||||
block.psk = match.groupValues[5].apply { check(length in 8..63) }
|
block.psk = match.groupValues[5].apply {
|
||||||
|
when (length) {
|
||||||
|
in 8..63 -> { }
|
||||||
|
64 -> error("WPA-PSK hex not supported")
|
||||||
|
else -> error("Unknown length $length")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
block.pskLine = block.size
|
block.pskLine = block.size
|
||||||
} else if (bssids.any { matchedBssid.equals(it, true) }) {
|
} else if (bssids.any { matchedBssid.equals(it, true) }) {
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package be.mygod.vpnhotspot.net.wifi
|
||||||
|
|
||||||
|
import android.os.Parcelable
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
import be.mygod.vpnhotspot.util.LongConstantLookup
|
||||||
|
|
||||||
|
@JvmInline
|
||||||
|
@RequiresApi(30)
|
||||||
|
value class SoftApCapability(val inner: Parcelable) {
|
||||||
|
companion object {
|
||||||
|
val clazz by lazy { Class.forName("android.net.wifi.SoftApCapability") }
|
||||||
|
private val getMaxSupportedClients by lazy { clazz.getDeclaredMethod("getMaxSupportedClients") }
|
||||||
|
private val areFeaturesSupported by lazy { clazz.getDeclaredMethod("areFeaturesSupported", Long::class.java) }
|
||||||
|
@get:RequiresApi(31)
|
||||||
|
private val getSupportedChannelList by lazy {
|
||||||
|
clazz.getDeclaredMethod("getSupportedChannelList", Int::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(31)
|
||||||
|
const val SOFTAP_FEATURE_BAND_24G_SUPPORTED = 32L
|
||||||
|
@RequiresApi(31)
|
||||||
|
const val SOFTAP_FEATURE_BAND_5G_SUPPORTED = 64L
|
||||||
|
@RequiresApi(31)
|
||||||
|
const val SOFTAP_FEATURE_BAND_6G_SUPPORTED = 128L
|
||||||
|
@RequiresApi(31)
|
||||||
|
const val SOFTAP_FEATURE_BAND_60G_SUPPORTED = 256L
|
||||||
|
val featureLookup by lazy { LongConstantLookup(clazz, "SOFTAP_FEATURE_") }
|
||||||
|
}
|
||||||
|
|
||||||
|
val maxSupportedClients get() = getMaxSupportedClients(inner) as Int
|
||||||
|
val supportedFeatures: Long get() {
|
||||||
|
var supportedFeatures = 0L
|
||||||
|
var probe = 1L
|
||||||
|
while (probe != 0L) {
|
||||||
|
if (areFeaturesSupported(inner, probe) as Boolean) supportedFeatures = supportedFeatures or probe
|
||||||
|
probe += probe
|
||||||
|
}
|
||||||
|
return supportedFeatures
|
||||||
|
}
|
||||||
|
fun getSupportedChannelList(band: Int) = getSupportedChannelList(inner, band) as IntArray
|
||||||
|
}
|
||||||
@@ -6,11 +6,15 @@ import android.net.MacAddress
|
|||||||
import android.net.wifi.SoftApConfiguration
|
import android.net.wifi.SoftApConfiguration
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
|
import android.util.SparseIntArray
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import be.mygod.vpnhotspot.net.MacAddressCompat
|
import be.mygod.vpnhotspot.net.MacAddressCompat
|
||||||
import be.mygod.vpnhotspot.net.MacAddressCompat.Companion.toCompat
|
import be.mygod.vpnhotspot.net.MacAddressCompat.Companion.toCompat
|
||||||
import be.mygod.vpnhotspot.net.monitor.TetherTimeoutMonitor
|
import be.mygod.vpnhotspot.net.monitor.TetherTimeoutMonitor
|
||||||
|
import be.mygod.vpnhotspot.util.ConstantLookup
|
||||||
|
import be.mygod.vpnhotspot.util.UnblockCentral
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class SoftApConfigurationCompat(
|
data class SoftApConfigurationCompat(
|
||||||
@@ -19,13 +23,18 @@ data class SoftApConfigurationCompat(
|
|||||||
var bssidAddr: Long? = null,
|
var bssidAddr: Long? = null,
|
||||||
var passphrase: String? = null,
|
var passphrase: String? = null,
|
||||||
var isHiddenSsid: Boolean = false,
|
var isHiddenSsid: Boolean = false,
|
||||||
|
/**
|
||||||
|
* To read legacy band/channel pair, use [requireSingleBand]. For easy access, see [getChannel].
|
||||||
|
*
|
||||||
|
* You should probably set or modify this field directly only when you want to use bridged AP,
|
||||||
|
* see also [android.net.wifi.WifiManager.isBridgedApConcurrencySupported].
|
||||||
|
* Otherwise, use [optimizeChannels] or [setChannel].
|
||||||
|
*/
|
||||||
@TargetApi(23)
|
@TargetApi(23)
|
||||||
var band: Int = BAND_2GHZ,
|
var channels: SparseIntArray = SparseIntArray(1).apply { append(BAND_2GHZ, 0) },
|
||||||
@TargetApi(23)
|
var securityType: Int = SoftApConfiguration.SECURITY_TYPE_OPEN,
|
||||||
var channel: Int = 0,
|
|
||||||
@TargetApi(30)
|
@TargetApi(30)
|
||||||
var maxNumberOfClients: Int = 0,
|
var maxNumberOfClients: Int = 0,
|
||||||
var securityType: Int = SoftApConfiguration.SECURITY_TYPE_OPEN,
|
|
||||||
@TargetApi(28)
|
@TargetApi(28)
|
||||||
var isAutoShutdownEnabled: Boolean = true,
|
var isAutoShutdownEnabled: Boolean = true,
|
||||||
@TargetApi(28)
|
@TargetApi(28)
|
||||||
@@ -36,12 +45,41 @@ data class SoftApConfigurationCompat(
|
|||||||
var blockedClientList: List<MacAddress> = emptyList(),
|
var blockedClientList: List<MacAddress> = emptyList(),
|
||||||
@RequiresApi(30)
|
@RequiresApi(30)
|
||||||
var allowedClientList: List<MacAddress> = emptyList(),
|
var allowedClientList: List<MacAddress> = emptyList(),
|
||||||
|
@TargetApi(31)
|
||||||
|
var macRandomizationSetting: Int = RANDOMIZATION_PERSISTENT,
|
||||||
|
@TargetApi(31)
|
||||||
|
var isBridgedModeOpportunisticShutdownEnabled: Boolean = true,
|
||||||
|
@TargetApi(31)
|
||||||
|
var isIeee80211axEnabled: Boolean = true,
|
||||||
|
@TargetApi(31)
|
||||||
|
var isUserConfiguration: Boolean = true,
|
||||||
var underlying: Parcelable? = null) : Parcelable {
|
var underlying: Parcelable? = null) : Parcelable {
|
||||||
companion object {
|
companion object {
|
||||||
const val BAND_2GHZ = 1
|
const val BAND_2GHZ = 1
|
||||||
const val BAND_5GHZ = 2
|
const val BAND_5GHZ = 2
|
||||||
|
@TargetApi(30)
|
||||||
const val BAND_6GHZ = 4
|
const val BAND_6GHZ = 4
|
||||||
const val BAND_ANY = 7
|
@TargetApi(31)
|
||||||
|
const val BAND_60GHZ = 8
|
||||||
|
const val BAND_LEGACY = BAND_2GHZ or BAND_5GHZ
|
||||||
|
val BAND_TYPES by lazy {
|
||||||
|
if (Build.VERSION.SDK_INT >= 31) try {
|
||||||
|
return@lazy UnblockCentral.SoftApConfiguration_BAND_TYPES
|
||||||
|
} catch (e: ReflectiveOperationException) {
|
||||||
|
Timber.w(e)
|
||||||
|
}
|
||||||
|
intArrayOf(BAND_2GHZ, BAND_5GHZ, BAND_6GHZ, BAND_60GHZ)
|
||||||
|
}
|
||||||
|
@RequiresApi(31)
|
||||||
|
val bandLookup = ConstantLookup<SoftApConfiguration>("BAND_")
|
||||||
|
|
||||||
|
@TargetApi(31)
|
||||||
|
const val RANDOMIZATION_NONE = 0
|
||||||
|
@TargetApi(31)
|
||||||
|
const val RANDOMIZATION_PERSISTENT = 1
|
||||||
|
|
||||||
|
fun isLegacyEitherBand(band: Int) = band and BAND_LEGACY == BAND_LEGACY
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [android.net.wifi.WifiConfiguration.KeyMgmt.WPA2_PSK]
|
* [android.net.wifi.WifiConfiguration.KeyMgmt.WPA2_PSK]
|
||||||
*/
|
*/
|
||||||
@@ -53,7 +91,8 @@ data class SoftApConfigurationCompat(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Based on:
|
* Based on:
|
||||||
* https://elixir.bootlin.com/linux/v5.7.6/source/net/wireless/util.c#L75
|
* https://elixir.bootlin.com/linux/v5.12.8/source/net/wireless/util.c#L75
|
||||||
|
* TODO: update for Android 12
|
||||||
* https://cs.android.com/android/platform/superproject/+/master:frameworks/base/wifi/java/android/net/wifi/ScanResult.java;l=624;drc=f7ccda05642b55700d67a288462bada488fc7f5e
|
* https://cs.android.com/android/platform/superproject/+/master:frameworks/base/wifi/java/android/net/wifi/ScanResult.java;l=624;drc=f7ccda05642b55700d67a288462bada488fc7f5e
|
||||||
*/
|
*/
|
||||||
fun channelToFrequency(band: Int, chan: Int) = when (band) {
|
fun channelToFrequency(band: Int, chan: Int) = when (band) {
|
||||||
@@ -67,18 +106,24 @@ data class SoftApConfigurationCompat(
|
|||||||
in 1..Int.MAX_VALUE -> 5000 + chan * 5
|
in 1..Int.MAX_VALUE -> 5000 + chan * 5
|
||||||
else -> throw IllegalArgumentException("Invalid 5GHz channel $chan")
|
else -> throw IllegalArgumentException("Invalid 5GHz channel $chan")
|
||||||
}
|
}
|
||||||
BAND_6GHZ -> if (chan in 1..253) {
|
BAND_6GHZ -> when (chan) {
|
||||||
5940 + chan * 5
|
2 -> 5935
|
||||||
} else throw IllegalArgumentException("Invalid 6GHz channel $chan")
|
in 1..233 -> 5950 + chan * 5
|
||||||
// BAND_60GHZ -> if (chan in 1 until 7) 56160 + chan * 2160
|
else -> throw IllegalArgumentException("Invalid 6GHz channel $chan")
|
||||||
|
}
|
||||||
|
BAND_60GHZ -> {
|
||||||
|
require(chan in 1 until 7) { "Invalid 60GHz channel $chan" }
|
||||||
|
56160 + chan * 2160
|
||||||
|
}
|
||||||
else -> throw IllegalArgumentException("Invalid band $band")
|
else -> throw IllegalArgumentException("Invalid band $band")
|
||||||
}
|
}
|
||||||
fun frequencyToChannel(freq: Int) = when (freq) {
|
fun frequencyToChannel(freq: Int) = when (freq) {
|
||||||
2484 -> 14
|
2484 -> 14
|
||||||
in Int.MIN_VALUE until 2484 -> (freq - 2407) / 5
|
in Int.MIN_VALUE until 2484 -> (freq - 2407) / 5
|
||||||
in 4910..4980 -> (freq - 4000) / 5
|
in 4910..4980 -> (freq - 4000) / 5
|
||||||
in Int.MIN_VALUE until 5945 -> (freq - 5000) / 5
|
in Int.MIN_VALUE until 5925 -> (freq - 5000) / 5
|
||||||
in Int.MIN_VALUE..45000 -> (freq - 5940) / 5
|
5935 -> 2
|
||||||
|
in Int.MIN_VALUE..45000 -> (freq - 5950) / 5
|
||||||
in 58320..70200 -> (freq - 56160) / 2160
|
in 58320..70200 -> (freq - 56160) / 2160
|
||||||
else -> throw IllegalArgumentException("Invalid frequency $freq")
|
else -> throw IllegalArgumentException("Invalid frequency $freq")
|
||||||
}
|
}
|
||||||
@@ -122,6 +167,14 @@ data class SoftApConfigurationCompat(
|
|||||||
private val getChannel by lazy @TargetApi(30) {
|
private val getChannel by lazy @TargetApi(30) {
|
||||||
SoftApConfiguration::class.java.getDeclaredMethod("getChannel")
|
SoftApConfiguration::class.java.getDeclaredMethod("getChannel")
|
||||||
}
|
}
|
||||||
|
@get:RequiresApi(31)
|
||||||
|
private val getChannels by lazy @TargetApi(31) {
|
||||||
|
SoftApConfiguration::class.java.getDeclaredMethod("getChannels")
|
||||||
|
}
|
||||||
|
@get:RequiresApi(31)
|
||||||
|
private val getMacRandomizationSetting by lazy @TargetApi(31) {
|
||||||
|
SoftApConfiguration::class.java.getDeclaredMethod("getMacRandomizationSetting")
|
||||||
|
}
|
||||||
@get:RequiresApi(30)
|
@get:RequiresApi(30)
|
||||||
private val getMaxNumberOfClients by lazy @TargetApi(30) {
|
private val getMaxNumberOfClients by lazy @TargetApi(30) {
|
||||||
SoftApConfiguration::class.java.getDeclaredMethod("getMaxNumberOfClients")
|
SoftApConfiguration::class.java.getDeclaredMethod("getMaxNumberOfClients")
|
||||||
@@ -134,10 +187,22 @@ data class SoftApConfigurationCompat(
|
|||||||
private val isAutoShutdownEnabled by lazy @TargetApi(30) {
|
private val isAutoShutdownEnabled by lazy @TargetApi(30) {
|
||||||
SoftApConfiguration::class.java.getDeclaredMethod("isAutoShutdownEnabled")
|
SoftApConfiguration::class.java.getDeclaredMethod("isAutoShutdownEnabled")
|
||||||
}
|
}
|
||||||
|
@get:RequiresApi(31)
|
||||||
|
private val isBridgedModeOpportunisticShutdownEnabled by lazy @TargetApi(31) {
|
||||||
|
SoftApConfiguration::class.java.getDeclaredMethod("isBridgedModeOpportunisticShutdownEnabled")
|
||||||
|
}
|
||||||
@get:RequiresApi(30)
|
@get:RequiresApi(30)
|
||||||
private val isClientControlByUserEnabled by lazy @TargetApi(30) {
|
private val isClientControlByUserEnabled by lazy @TargetApi(30) {
|
||||||
SoftApConfiguration::class.java.getDeclaredMethod("isClientControlByUserEnabled")
|
SoftApConfiguration::class.java.getDeclaredMethod("isClientControlByUserEnabled")
|
||||||
}
|
}
|
||||||
|
@get:RequiresApi(31)
|
||||||
|
private val isIeee80211axEnabled by lazy @TargetApi(31) {
|
||||||
|
SoftApConfiguration::class.java.getDeclaredMethod("isIeee80211axEnabled")
|
||||||
|
}
|
||||||
|
@get:RequiresApi(31)
|
||||||
|
private val isUserConfiguration by lazy @TargetApi(31) {
|
||||||
|
SoftApConfiguration::class.java.getDeclaredMethod("isUserConfiguration")
|
||||||
|
}
|
||||||
|
|
||||||
@get:RequiresApi(30)
|
@get:RequiresApi(30)
|
||||||
private val classBuilder by lazy { Class.forName("android.net.wifi.SoftApConfiguration\$Builder") }
|
private val classBuilder by lazy { Class.forName("android.net.wifi.SoftApConfiguration\$Builder") }
|
||||||
@@ -159,6 +224,10 @@ data class SoftApConfigurationCompat(
|
|||||||
private val setBlockedClientList by lazy {
|
private val setBlockedClientList by lazy {
|
||||||
classBuilder.getDeclaredMethod("setBlockedClientList", java.util.List::class.java)
|
classBuilder.getDeclaredMethod("setBlockedClientList", java.util.List::class.java)
|
||||||
}
|
}
|
||||||
|
@get:RequiresApi(31)
|
||||||
|
private val setBridgedModeOpportunisticShutdownEnabled by lazy {
|
||||||
|
classBuilder.getDeclaredMethod("setBridgedModeOpportunisticShutdownEnabled", Boolean::class.java)
|
||||||
|
}
|
||||||
@get:RequiresApi(30)
|
@get:RequiresApi(30)
|
||||||
private val setBssid by lazy @TargetApi(30) {
|
private val setBssid by lazy @TargetApi(30) {
|
||||||
classBuilder.getDeclaredMethod("setBssid", MacAddress::class.java)
|
classBuilder.getDeclaredMethod("setBssid", MacAddress::class.java)
|
||||||
@@ -167,12 +236,24 @@ data class SoftApConfigurationCompat(
|
|||||||
private val setChannel by lazy {
|
private val setChannel by lazy {
|
||||||
classBuilder.getDeclaredMethod("setChannel", Int::class.java, Int::class.java)
|
classBuilder.getDeclaredMethod("setChannel", Int::class.java, Int::class.java)
|
||||||
}
|
}
|
||||||
|
@get:RequiresApi(31)
|
||||||
|
private val setChannels by lazy {
|
||||||
|
classBuilder.getDeclaredMethod("setChannels", SparseIntArray::class.java)
|
||||||
|
}
|
||||||
@get:RequiresApi(30)
|
@get:RequiresApi(30)
|
||||||
private val setClientControlByUserEnabled by lazy {
|
private val setClientControlByUserEnabled by lazy {
|
||||||
classBuilder.getDeclaredMethod("setClientControlByUserEnabled", Boolean::class.java)
|
classBuilder.getDeclaredMethod("setClientControlByUserEnabled", Boolean::class.java)
|
||||||
}
|
}
|
||||||
@get:RequiresApi(30)
|
@get:RequiresApi(30)
|
||||||
private val setHiddenSsid by lazy { classBuilder.getDeclaredMethod("setHiddenSsid", Boolean::class.java) }
|
private val setHiddenSsid by lazy { classBuilder.getDeclaredMethod("setHiddenSsid", Boolean::class.java) }
|
||||||
|
@get:RequiresApi(31)
|
||||||
|
private val setIeee80211axEnabled by lazy {
|
||||||
|
classBuilder.getDeclaredMethod("setIeee80211axEnabled", Boolean::class.java)
|
||||||
|
}
|
||||||
|
@get:RequiresApi(31)
|
||||||
|
private val setMacRandomizationSetting by lazy {
|
||||||
|
classBuilder.getDeclaredMethod("setMacRandomizationSetting", Int::class.java)
|
||||||
|
}
|
||||||
@get:RequiresApi(30)
|
@get:RequiresApi(30)
|
||||||
private val setMaxNumberOfClients by lazy {
|
private val setMaxNumberOfClients by lazy {
|
||||||
classBuilder.getDeclaredMethod("setMaxNumberOfClients", Int::class.java)
|
classBuilder.getDeclaredMethod("setMaxNumberOfClients", Int::class.java)
|
||||||
@@ -187,6 +268,8 @@ data class SoftApConfigurationCompat(
|
|||||||
}
|
}
|
||||||
@get:RequiresApi(30)
|
@get:RequiresApi(30)
|
||||||
private val setSsid by lazy { classBuilder.getDeclaredMethod("setSsid", String::class.java) }
|
private val setSsid by lazy { classBuilder.getDeclaredMethod("setSsid", String::class.java) }
|
||||||
|
@get:RequiresApi(31)
|
||||||
|
private val setUserConfiguration by lazy @TargetApi(31) { UnblockCentral.setUserConfiguration(classBuilder) }
|
||||||
|
|
||||||
@Deprecated("Class deprecated in framework")
|
@Deprecated("Class deprecated in framework")
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
@@ -196,14 +279,14 @@ data class SoftApConfigurationCompat(
|
|||||||
preSharedKey,
|
preSharedKey,
|
||||||
hiddenSSID,
|
hiddenSSID,
|
||||||
// https://cs.android.com/android/platform/superproject/+/master:frameworks/base/wifi/java/android/net/wifi/SoftApConfToXmlMigrationUtil.java;l=87;drc=aa6527cf41671d1ed417b8ebdb6b3aa614f62344
|
// https://cs.android.com/android/platform/superproject/+/master:frameworks/base/wifi/java/android/net/wifi/SoftApConfToXmlMigrationUtil.java;l=87;drc=aa6527cf41671d1ed417b8ebdb6b3aa614f62344
|
||||||
if (Build.VERSION.SDK_INT >= 23) when (val band = apBand.getInt(this)) {
|
SparseIntArray(1).also {
|
||||||
0 -> BAND_2GHZ
|
if (Build.VERSION.SDK_INT >= 23) it.append(when (val band = apBand.getInt(this)) {
|
||||||
1 -> BAND_5GHZ
|
0 -> BAND_2GHZ
|
||||||
-1 -> BAND_2GHZ or BAND_5GHZ
|
1 -> BAND_5GHZ
|
||||||
else -> throw IllegalArgumentException("Unexpected band $band")
|
-1 -> BAND_LEGACY
|
||||||
} else BAND_ANY,
|
else -> throw IllegalArgumentException("Unexpected band $band")
|
||||||
if (Build.VERSION.SDK_INT >= 23) apChannel.getInt(this) else 0,
|
}, apChannel.getInt(this)) else it.append(BAND_LEGACY, 0)
|
||||||
0,
|
},
|
||||||
allowedKeyManagement.nextSetBit(0).let { selected ->
|
allowedKeyManagement.nextSetBit(0).let { selected ->
|
||||||
require(allowedKeyManagement.nextSetBit(selected + 1) < 0) {
|
require(allowedKeyManagement.nextSetBit(selected + 1) < 0) {
|
||||||
"More than 1 key managements supplied: $allowedKeyManagement"
|
"More than 1 key managements supplied: $allowedKeyManagement"
|
||||||
@@ -224,26 +307,32 @@ data class SoftApConfigurationCompat(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
if (Build.VERSION.SDK_INT >= 28) TetherTimeoutMonitor.enabled else false,
|
isAutoShutdownEnabled = if (Build.VERSION.SDK_INT >= 28) TetherTimeoutMonitor.enabled else false,
|
||||||
underlying = this)
|
underlying = this)
|
||||||
|
|
||||||
@RequiresApi(30)
|
@RequiresApi(30)
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
fun SoftApConfiguration.toCompat() = SoftApConfigurationCompat(
|
fun SoftApConfiguration.toCompat() = SoftApConfigurationCompat(
|
||||||
ssid,
|
ssid,
|
||||||
bssid?.toCompat()?.addr,
|
bssid?.toCompat()?.addr,
|
||||||
passphrase,
|
passphrase,
|
||||||
isHiddenSsid,
|
isHiddenSsid,
|
||||||
getBand(this) as Int,
|
if (Build.VERSION.SDK_INT >= 31) getChannels(this) as SparseIntArray else SparseIntArray(1).also {
|
||||||
getChannel(this) as Int,
|
it.append(getBand(this) as Int, getChannel(this) as Int)
|
||||||
getMaxNumberOfClients(this) as Int,
|
},
|
||||||
securityType,
|
securityType,
|
||||||
isAutoShutdownEnabled(this) as Boolean,
|
getMaxNumberOfClients(this) as Int,
|
||||||
getShutdownTimeoutMillis(this) as Long,
|
isAutoShutdownEnabled(this) as Boolean,
|
||||||
isClientControlByUserEnabled(this) as Boolean,
|
getShutdownTimeoutMillis(this) as Long,
|
||||||
getBlockedClientList(this) as List<MacAddress>,
|
isClientControlByUserEnabled(this) as Boolean,
|
||||||
getAllowedClientList(this) as List<MacAddress>,
|
getBlockedClientList(this) as List<MacAddress>,
|
||||||
this)
|
getAllowedClientList(this) as List<MacAddress>,
|
||||||
|
if (Build.VERSION.SDK_INT >= 31) getMacRandomizationSetting(this) as Int else RANDOMIZATION_PERSISTENT,
|
||||||
|
Build.VERSION.SDK_INT < 31 || isBridgedModeOpportunisticShutdownEnabled(this) as Boolean,
|
||||||
|
Build.VERSION.SDK_INT < 31 || isIeee80211axEnabled(this) as Boolean,
|
||||||
|
Build.VERSION.SDK_INT < 31 || isUserConfiguration(this) as Boolean,
|
||||||
|
this,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
@@ -253,6 +342,47 @@ data class SoftApConfigurationCompat(
|
|||||||
bssidAddr = value?.addr
|
bssidAddr = value?.addr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only single band/channel can be supplied on API 23-30
|
||||||
|
*/
|
||||||
|
fun requireSingleBand(): Pair<Int, Int> {
|
||||||
|
require(channels.size() == 1) { "Unsupported number of bands configured" }
|
||||||
|
return channels.keyAt(0) to channels.valueAt(0)
|
||||||
|
}
|
||||||
|
fun getChannel(band: Int): Int {
|
||||||
|
var result = -1
|
||||||
|
repeat(channels.size()) { i ->
|
||||||
|
if (band and channels.keyAt(i) != band) return@repeat
|
||||||
|
require(result == -1) { "Duplicate band found" }
|
||||||
|
result = channels.valueAt(i)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
fun setChannel(channel: Int, band: Int = BAND_LEGACY) {
|
||||||
|
channels = SparseIntArray(1).apply {
|
||||||
|
append(when {
|
||||||
|
channel <= 0 || band != BAND_LEGACY -> band
|
||||||
|
channel > 14 -> BAND_5GHZ
|
||||||
|
else -> BAND_2GHZ
|
||||||
|
}, channel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun optimizeChannels(channels: SparseIntArray = this.channels) {
|
||||||
|
this.channels = SparseIntArray(channels.size()).apply {
|
||||||
|
var setBand = 0
|
||||||
|
repeat(channels.size()) { i -> if (channels.valueAt(i) == 0) setBand = setBand or channels.keyAt(i) }
|
||||||
|
if (setBand != 0) append(setBand, 0) // merge all bands into one
|
||||||
|
repeat(channels.size()) { i ->
|
||||||
|
val band = channels.keyAt(i)
|
||||||
|
if (band and setBand == 0) put(band, channels.valueAt(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setMacRandomizationEnabled(enabled: Boolean) {
|
||||||
|
macRandomizationSetting = if (enabled) RANDOMIZATION_PERSISTENT else RANDOMIZATION_NONE
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Based on:
|
* Based on:
|
||||||
* https://android.googlesource.com/platform/packages/apps/Settings/+/android-5.0.0_r1/src/com/android/settings/wifi/WifiApDialog.java#88
|
* https://android.googlesource.com/platform/packages/apps/Settings/+/android-5.0.0_r1/src/com/android/settings/wifi/WifiApDialog.java#88
|
||||||
@@ -263,6 +393,7 @@ data class SoftApConfigurationCompat(
|
|||||||
@Deprecated("Class deprecated in framework, use toPlatform().toWifiConfiguration()")
|
@Deprecated("Class deprecated in framework, use toPlatform().toWifiConfiguration()")
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
fun toWifiConfiguration(): android.net.wifi.WifiConfiguration {
|
fun toWifiConfiguration(): android.net.wifi.WifiConfiguration {
|
||||||
|
val (band, channel) = requireSingleBand()
|
||||||
val wc = underlying as? android.net.wifi.WifiConfiguration
|
val wc = underlying as? android.net.wifi.WifiConfiguration
|
||||||
val result = if (wc == null) android.net.wifi.WifiConfiguration() else android.net.wifi.WifiConfiguration(wc)
|
val result = if (wc == null) android.net.wifi.WifiConfiguration() else android.net.wifi.WifiConfiguration(wc)
|
||||||
val original = wc?.toCompat()
|
val original = wc?.toCompat()
|
||||||
@@ -273,11 +404,14 @@ data class SoftApConfigurationCompat(
|
|||||||
apBand.setInt(result, when (band) {
|
apBand.setInt(result, when (band) {
|
||||||
BAND_2GHZ -> 0
|
BAND_2GHZ -> 0
|
||||||
BAND_5GHZ -> 1
|
BAND_5GHZ -> 1
|
||||||
BAND_2GHZ or BAND_5GHZ, BAND_ANY -> -1
|
else -> {
|
||||||
else -> throw IllegalArgumentException("Convert fail, unsupported band setting :$band")
|
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)
|
apChannel.setInt(result, channel)
|
||||||
} else require(band == BAND_ANY) { "Specifying band is unsupported on this platform" }
|
} else require(isLegacyEitherBand(band)) { "Specifying band is unsupported on this platform" }
|
||||||
if (original?.securityType != securityType) {
|
if (original?.securityType != securityType) {
|
||||||
result.allowedKeyManagement.clear()
|
result.allowedKeyManagement.clear()
|
||||||
result.allowedKeyManagement.set(when (securityType) {
|
result.allowedKeyManagement.set(when (securityType) {
|
||||||
@@ -304,7 +438,10 @@ data class SoftApConfigurationCompat(
|
|||||||
setSsid(builder, ssid)
|
setSsid(builder, ssid)
|
||||||
setPassphrase(builder, if (securityType == SoftApConfiguration.SECURITY_TYPE_OPEN) null else passphrase,
|
setPassphrase(builder, if (securityType == SoftApConfiguration.SECURITY_TYPE_OPEN) null else passphrase,
|
||||||
securityType)
|
securityType)
|
||||||
if (channel == 0) setBand(builder, band) else setChannel(builder, channel, band)
|
if (Build.VERSION.SDK_INT >= 31) setChannels(builder, channels) else {
|
||||||
|
val (band, channel) = requireSingleBand()
|
||||||
|
if (channel == 0) setBand(builder, band) else setChannel(builder, channel, band)
|
||||||
|
}
|
||||||
setBssid(builder, bssid?.toPlatform())
|
setBssid(builder, bssid?.toPlatform())
|
||||||
setMaxNumberOfClients(builder, maxNumberOfClients)
|
setMaxNumberOfClients(builder, maxNumberOfClients)
|
||||||
setShutdownTimeoutMillis(builder, shutdownTimeoutMillis)
|
setShutdownTimeoutMillis(builder, shutdownTimeoutMillis)
|
||||||
@@ -313,6 +450,16 @@ data class SoftApConfigurationCompat(
|
|||||||
setHiddenSsid(builder, isHiddenSsid)
|
setHiddenSsid(builder, isHiddenSsid)
|
||||||
setAllowedClientList(builder, allowedClientList)
|
setAllowedClientList(builder, allowedClientList)
|
||||||
setBlockedClientList(builder, blockedClientList)
|
setBlockedClientList(builder, blockedClientList)
|
||||||
|
if (Build.VERSION.SDK_INT >= 31) {
|
||||||
|
setMacRandomizationSetting(builder, macRandomizationSetting)
|
||||||
|
setBridgedModeOpportunisticShutdownEnabled(builder, isBridgedModeOpportunisticShutdownEnabled)
|
||||||
|
setIeee80211axEnabled(builder, isIeee80211axEnabled)
|
||||||
|
if (sac?.let { isUserConfiguration(it) as Boolean } != false != isUserConfiguration) try {
|
||||||
|
setUserConfiguration(builder, isUserConfiguration)
|
||||||
|
} catch (e: ReflectiveOperationException) {
|
||||||
|
Timber.w(e) // as far as we are concerned, this field is not used anywhere so ignore for now
|
||||||
|
}
|
||||||
|
}
|
||||||
return build(builder) as SoftApConfiguration
|
return build(builder) as SoftApConfiguration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package be.mygod.vpnhotspot.net.wifi
|
||||||
|
|
||||||
|
import android.annotation.TargetApi
|
||||||
|
import android.net.MacAddress
|
||||||
|
import android.os.Parcelable
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
import be.mygod.vpnhotspot.util.ConstantLookup
|
||||||
|
import be.mygod.vpnhotspot.util.UnblockCentral
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
@JvmInline
|
||||||
|
@RequiresApi(30)
|
||||||
|
value class SoftApInfo(val inner: Parcelable) {
|
||||||
|
companion object {
|
||||||
|
val clazz by lazy { Class.forName("android.net.wifi.SoftApInfo") }
|
||||||
|
private val getFrequency by lazy { clazz.getDeclaredMethod("getFrequency") }
|
||||||
|
private val getBandwidth by lazy { clazz.getDeclaredMethod("getBandwidth") }
|
||||||
|
@get:RequiresApi(31)
|
||||||
|
private val getBssid by lazy { clazz.getDeclaredMethod("getBssid") }
|
||||||
|
@get:RequiresApi(31)
|
||||||
|
private val getWifiStandard by lazy { clazz.getDeclaredMethod("getWifiStandard") }
|
||||||
|
@get:RequiresApi(31)
|
||||||
|
private val getApInstanceIdentifier by lazy @TargetApi(31) { UnblockCentral.getApInstanceIdentifier(clazz) }
|
||||||
|
@get:RequiresApi(31)
|
||||||
|
private val getAutoShutdownTimeoutMillis by lazy { clazz.getDeclaredMethod("getAutoShutdownTimeoutMillis") }
|
||||||
|
|
||||||
|
val channelWidthLookup = ConstantLookup("CHANNEL_WIDTH_") { clazz }
|
||||||
|
}
|
||||||
|
|
||||||
|
val frequency get() = getFrequency(inner) as Int
|
||||||
|
val bandwidth get() = getBandwidth(inner) as Int
|
||||||
|
@get:RequiresApi(31)
|
||||||
|
val bssid get() = getBssid(inner) as MacAddress?
|
||||||
|
@get:RequiresApi(31)
|
||||||
|
val wifiStandard get() = getWifiStandard(inner) as Int
|
||||||
|
@get:RequiresApi(31)
|
||||||
|
val apInstanceIdentifier get() = try {
|
||||||
|
getApInstanceIdentifier(inner) as? String
|
||||||
|
} catch (e: ReflectiveOperationException) {
|
||||||
|
Timber.w(e)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
@get:RequiresApi(31)
|
||||||
|
val autoShutdownTimeoutMillis get() = getAutoShutdownTimeoutMillis(inner) as Long
|
||||||
|
}
|
||||||
@@ -10,10 +10,13 @@ import android.os.Parcelable
|
|||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
import android.text.TextWatcher
|
import android.text.TextWatcher
|
||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
|
import android.util.SparseIntArray
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.AdapterView
|
import android.widget.AdapterView
|
||||||
import android.widget.ArrayAdapter
|
import android.widget.ArrayAdapter
|
||||||
|
import android.widget.Spinner
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.widget.Toolbar
|
import androidx.appcompat.widget.Toolbar
|
||||||
@@ -30,8 +33,8 @@ import be.mygod.vpnhotspot.net.monitor.TetherTimeoutMonitor
|
|||||||
import be.mygod.vpnhotspot.util.QRCodeDialog
|
import be.mygod.vpnhotspot.util.QRCodeDialog
|
||||||
import be.mygod.vpnhotspot.util.readableMessage
|
import be.mygod.vpnhotspot.util.readableMessage
|
||||||
import be.mygod.vpnhotspot.util.showAllowingStateLoss
|
import be.mygod.vpnhotspot.util.showAllowingStateLoss
|
||||||
import be.mygod.vpnhotspot.widget.SmartSnackbar
|
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Based on: https://android.googlesource.com/platform/packages/apps/Settings/+/39b4674/src/com/android/settings/wifi/WifiApDialog.java
|
* Based on: https://android.googlesource.com/platform/packages/apps/Settings/+/39b4674/src/com/android/settings/wifi/WifiApDialog.java
|
||||||
@@ -40,27 +43,30 @@ import kotlinx.parcelize.Parcelize
|
|||||||
* Related: https://android.googlesource.com/platform/packages/apps/Settings/+/defb1183ecb00d6231bac7d934d07f58f90261ea
|
* Related: https://android.googlesource.com/platform/packages/apps/Settings/+/defb1183ecb00d6231bac7d934d07f58f90261ea
|
||||||
*/
|
*/
|
||||||
class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiApDialogFragment.Arg>(), TextWatcher,
|
class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiApDialogFragment.Arg>(), TextWatcher,
|
||||||
Toolbar.OnMenuItemClickListener {
|
Toolbar.OnMenuItemClickListener, AdapterView.OnItemSelectedListener {
|
||||||
companion object {
|
companion object {
|
||||||
private const val BASE64_FLAGS = Base64.NO_PADDING or Base64.NO_WRAP
|
private const val BASE64_FLAGS = Base64.NO_PADDING or Base64.NO_WRAP
|
||||||
private val nonMacChars = "[^0-9a-fA-F:]+".toRegex()
|
private val nonMacChars = "[^0-9a-fA-F:]+".toRegex()
|
||||||
private val channels by lazy {
|
private val baseOptions by lazy { listOf(ChannelOption.Disabled, ChannelOption.Auto) }
|
||||||
val list = ArrayList<BandOption.Channel>()
|
private val channels2G by lazy {
|
||||||
for (chan in 1..14) list.add(BandOption.Channel(SoftApConfigurationCompat.BAND_2GHZ, chan))
|
baseOptions + (1..14).map { ChannelOption(it, SoftApConfigurationCompat.BAND_2GHZ) }
|
||||||
for (chan in 1..196) list.add(BandOption.Channel(SoftApConfigurationCompat.BAND_5GHZ, chan))
|
}
|
||||||
if (Build.VERSION.SDK_INT >= 30) {
|
private val channels5G by lazy {
|
||||||
for (chan in 1..253) list.add(BandOption.Channel(SoftApConfigurationCompat.BAND_6GHZ, chan))
|
baseOptions + (1..196).map { ChannelOption(it, SoftApConfigurationCompat.BAND_5GHZ) }
|
||||||
}
|
}
|
||||||
list
|
@get:RequiresApi(30)
|
||||||
|
private val channels6G by lazy {
|
||||||
|
baseOptions + (1..233).map { ChannelOption(it, SoftApConfigurationCompat.BAND_6GHZ) }
|
||||||
|
}
|
||||||
|
@get:RequiresApi(31)
|
||||||
|
private val channels60G by lazy {
|
||||||
|
baseOptions + (1..6).map { ChannelOption(it, SoftApConfigurationCompat.BAND_60GHZ) }
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Source: https://android.googlesource.com/platform/frameworks/opt/net/wifi/+/c2fc6a1/service/java/com/android/server/wifi/p2p/SupplicantP2pIfaceHal.java#1396
|
* Source: https://android.googlesource.com/platform/frameworks/opt/net/wifi/+/c2fc6a1/service/java/com/android/server/wifi/p2p/SupplicantP2pIfaceHal.java#1396
|
||||||
*/
|
*/
|
||||||
private val p2pChannels by lazy {
|
private val p2pChannels by lazy {
|
||||||
(1..165).map {
|
baseOptions + (15..165).map { ChannelOption(it, SoftApConfigurationCompat.BAND_5GHZ) }
|
||||||
val band = if (it <= 14) SoftApConfigurationCompat.BAND_2GHZ else SoftApConfigurationCompat.BAND_5GHZ
|
|
||||||
BandOption.Channel(band, it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,35 +79,20 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
|
|||||||
*/
|
*/
|
||||||
val p2pMode: Boolean = false) : Parcelable
|
val p2pMode: Boolean = false) : Parcelable
|
||||||
|
|
||||||
private sealed class BandOption {
|
private open class ChannelOption(val channel: Int = 0, private val band: Int = 0) {
|
||||||
open val band get() = SoftApConfigurationCompat.BAND_ANY
|
object Disabled : ChannelOption(-1) {
|
||||||
open val channel get() = 0
|
override fun toString() = app.getString(R.string.wifi_ap_choose_disabled)
|
||||||
|
}
|
||||||
object BandAny : BandOption() {
|
object Auto : ChannelOption() {
|
||||||
override fun toString() = app.getString(R.string.wifi_ap_choose_auto)
|
override fun toString() = app.getString(R.string.wifi_ap_choose_auto)
|
||||||
}
|
}
|
||||||
object Band2GHz : BandOption() {
|
override fun toString() = "${SoftApConfigurationCompat.channelToFrequency(band, channel)} MHz ($channel)"
|
||||||
override val band get() = SoftApConfigurationCompat.BAND_2GHZ
|
|
||||||
override fun toString() = app.getString(R.string.wifi_ap_choose_2G)
|
|
||||||
}
|
|
||||||
object Band5GHz : BandOption() {
|
|
||||||
override val band get() = SoftApConfigurationCompat.BAND_5GHZ
|
|
||||||
override fun toString() = app.getString(R.string.wifi_ap_choose_5G)
|
|
||||||
}
|
|
||||||
@RequiresApi(30)
|
|
||||||
object Band6GHz : BandOption() {
|
|
||||||
override val band get() = SoftApConfigurationCompat.BAND_6GHZ
|
|
||||||
override fun toString() = app.getString(R.string.wifi_ap_choose_6G)
|
|
||||||
}
|
|
||||||
class Channel(override val band: Int, override val channel: Int) : BandOption() {
|
|
||||||
override fun toString() = "${SoftApConfigurationCompat.channelToFrequency(band, channel)} MHz ($channel)"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private lateinit var dialogView: DialogWifiApBinding
|
private lateinit var dialogView: DialogWifiApBinding
|
||||||
private lateinit var bandOptions: MutableList<BandOption>
|
|
||||||
private lateinit var base: SoftApConfigurationCompat
|
private lateinit var base: SoftApConfigurationCompat
|
||||||
private var started = false
|
private var started = false
|
||||||
|
private val currentChannels5G get() = if (arg.p2pMode && !RepeaterService.safeMode) p2pChannels else channels5G
|
||||||
override val ret get() = Arg(generateConfig())
|
override val ret get() = Arg(generateConfig())
|
||||||
|
|
||||||
private fun generateConfig(full: Boolean = true) = base.copy(
|
private fun generateConfig(full: Boolean = true) = base.copy(
|
||||||
@@ -117,9 +108,17 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
|
|||||||
if (text.isNullOrEmpty()) 0 else text.toString().toLong()
|
if (text.isNullOrEmpty()) 0 else text.toString().toLong()
|
||||||
}
|
}
|
||||||
if (Build.VERSION.SDK_INT >= 23 || arg.p2pMode) {
|
if (Build.VERSION.SDK_INT >= 23 || arg.p2pMode) {
|
||||||
val bandOption = dialogView.band.selectedItem as BandOption
|
val channels = SparseIntArray(4)
|
||||||
band = bandOption.band
|
for ((band, spinner) in arrayOf(SoftApConfigurationCompat.BAND_2GHZ to dialogView.band2G,
|
||||||
channel = bandOption.channel
|
SoftApConfigurationCompat.BAND_5GHZ to dialogView.band5G,
|
||||||
|
SoftApConfigurationCompat.BAND_6GHZ to dialogView.band6G,
|
||||||
|
SoftApConfigurationCompat.BAND_60GHZ to dialogView.band60G)) {
|
||||||
|
val channel = (spinner.selectedItem as ChannelOption?)?.channel
|
||||||
|
if (channel != null && channel >= 0) channels.append(band, channel)
|
||||||
|
}
|
||||||
|
if (!arg.p2pMode && Build.VERSION.SDK_INT >= 31 && dialogView.bridgedMode.isChecked) {
|
||||||
|
this.channels = channels
|
||||||
|
} else optimizeChannels(channels)
|
||||||
}
|
}
|
||||||
bssid = if (dialogView.bssid.length() != 0) {
|
bssid = if (dialogView.bssid.length() != 0) {
|
||||||
MacAddressCompat.fromString(dialogView.bssid.text.toString())
|
MacAddressCompat.fromString(dialogView.bssid.text.toString())
|
||||||
@@ -132,6 +131,10 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
|
|||||||
.filter { it.isNotEmpty() }.map { MacAddressCompat.fromString(it).toPlatform() }
|
.filter { it.isNotEmpty() }.map { MacAddressCompat.fromString(it).toPlatform() }
|
||||||
blockedClientList = (dialogView.blockedList.text ?: "").split(nonMacChars)
|
blockedClientList = (dialogView.blockedList.text ?: "").split(nonMacChars)
|
||||||
.filter { it.isNotEmpty() }.map { MacAddressCompat.fromString(it).toPlatform() }
|
.filter { it.isNotEmpty() }.map { MacAddressCompat.fromString(it).toPlatform() }
|
||||||
|
setMacRandomizationEnabled(dialogView.macRandomization.isChecked)
|
||||||
|
isBridgedModeOpportunisticShutdownEnabled = dialogView.bridgedModeOpportunisticShutdown.isChecked
|
||||||
|
isIeee80211axEnabled = dialogView.ieee80211ax.isChecked
|
||||||
|
isUserConfiguration = dialogView.userConfig.isChecked
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,43 +167,69 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
|
|||||||
TetherTimeoutMonitor.defaultTimeout)
|
TetherTimeoutMonitor.defaultTimeout)
|
||||||
dialogView.timeout.addTextChangedListener(this@WifiApDialogFragment)
|
dialogView.timeout.addTextChangedListener(this@WifiApDialogFragment)
|
||||||
} else dialogView.timeoutWrapper.isGone = true
|
} else dialogView.timeoutWrapper.isGone = true
|
||||||
if (Build.VERSION.SDK_INT >= 23 || arg.p2pMode) dialogView.band.apply {
|
fun Spinner.configure(options: List<ChannelOption>) {
|
||||||
bandOptions = mutableListOf<BandOption>().apply {
|
adapter = ArrayAdapter(activity, android.R.layout.simple_spinner_item, 0, options).apply {
|
||||||
if (arg.p2pMode) {
|
|
||||||
add(BandOption.BandAny)
|
|
||||||
if (RepeaterService.safeMode) {
|
|
||||||
add(BandOption.Band2GHz)
|
|
||||||
add(BandOption.Band5GHz)
|
|
||||||
addAll(channels)
|
|
||||||
} else addAll(p2pChannels)
|
|
||||||
} else {
|
|
||||||
if (Build.VERSION.SDK_INT >= 28) add(BandOption.BandAny)
|
|
||||||
add(BandOption.Band2GHz)
|
|
||||||
add(BandOption.Band5GHz)
|
|
||||||
if (Build.VERSION.SDK_INT >= 30) add(BandOption.Band6GHz)
|
|
||||||
addAll(channels)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
adapter = ArrayAdapter(activity, android.R.layout.simple_spinner_item, 0, bandOptions).apply {
|
|
||||||
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||||
}
|
}
|
||||||
} else dialogView.bandWrapper.isGone = true
|
onItemSelectedListener = this@WifiApDialogFragment
|
||||||
dialogView.bssid.addTextChangedListener(this@WifiApDialogFragment)
|
}
|
||||||
if (arg.p2pMode) dialogView.hiddenSsid.isGone = true
|
if (Build.VERSION.SDK_INT >= 23 || arg.p2pMode) {
|
||||||
if (arg.p2pMode || Build.VERSION.SDK_INT < 30) {
|
dialogView.band2G.configure(channels2G)
|
||||||
dialogView.maxClientWrapper.isGone = true
|
dialogView.band5G.configure(currentChannels5G)
|
||||||
dialogView.clientUserControl.isGone = true
|
if (Build.VERSION.SDK_INT >= 30 && !arg.p2pMode) dialogView.band6G.configure(channels6G)
|
||||||
dialogView.blockedListWrapper.isGone = true
|
else dialogView.bandWrapper6G.isGone = true
|
||||||
dialogView.allowedListWrapper.isGone = true
|
if (Build.VERSION.SDK_INT >= 31 && !arg.p2pMode) dialogView.band60G.configure(channels60G) else {
|
||||||
} else {
|
dialogView.bandWrapper60G.isGone = true
|
||||||
|
dialogView.bridgedMode.isGone = true
|
||||||
|
dialogView.bridgedModeOpportunisticShutdown.isGone = true
|
||||||
|
}
|
||||||
|
} else dialogView.bandGroup.isGone = true
|
||||||
|
if (!arg.p2pMode && Build.VERSION.SDK_INT >= 30) {
|
||||||
dialogView.maxClient.addTextChangedListener(this@WifiApDialogFragment)
|
dialogView.maxClient.addTextChangedListener(this@WifiApDialogFragment)
|
||||||
dialogView.blockedList.addTextChangedListener(this@WifiApDialogFragment)
|
dialogView.blockedList.addTextChangedListener(this@WifiApDialogFragment)
|
||||||
dialogView.allowedList.addTextChangedListener(this@WifiApDialogFragment)
|
dialogView.allowedList.addTextChangedListener(this@WifiApDialogFragment)
|
||||||
|
} else dialogView.accessControlGroup.isGone = true
|
||||||
|
dialogView.bssid.addTextChangedListener(this@WifiApDialogFragment)
|
||||||
|
if (arg.p2pMode) dialogView.hiddenSsid.isGone = true
|
||||||
|
if (arg.p2pMode && Build.VERSION.SDK_INT >= 29) dialogView.macRandomization.isEnabled = false
|
||||||
|
else if (arg.p2pMode || Build.VERSION.SDK_INT < 31) dialogView.macRandomization.isGone = true
|
||||||
|
if (arg.p2pMode || Build.VERSION.SDK_INT < 31) {
|
||||||
|
dialogView.ieee80211ax.isGone = true
|
||||||
|
dialogView.userConfig.isGone = true
|
||||||
}
|
}
|
||||||
base = arg.configuration
|
base = arg.configuration
|
||||||
populateFromConfiguration()
|
populateFromConfiguration()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun locate(band: Int, channels: List<ChannelOption>): Int {
|
||||||
|
val channel = base.getChannel(band)
|
||||||
|
val selection = channels.indexOfFirst { it.channel == channel }
|
||||||
|
return if (selection == -1) {
|
||||||
|
Timber.w(Exception("Unable to locate $band, $channel, ${arg.p2pMode && !RepeaterService.safeMode}"))
|
||||||
|
0
|
||||||
|
} else selection
|
||||||
|
}
|
||||||
|
private var userBridgedMode = false
|
||||||
|
private fun setBridgedMode(): Boolean {
|
||||||
|
var auto = 0
|
||||||
|
var set = 0
|
||||||
|
for (s in arrayOf(dialogView.band2G, dialogView.band5G, dialogView.band6G,
|
||||||
|
dialogView.band60G)) when (s.selectedItem) {
|
||||||
|
is ChannelOption.Auto -> auto = 1
|
||||||
|
!is ChannelOption.Disabled -> ++set
|
||||||
|
}
|
||||||
|
if (auto + set > 1) {
|
||||||
|
if (dialogView.bridgedMode.isEnabled) {
|
||||||
|
userBridgedMode = dialogView.bridgedMode.isChecked
|
||||||
|
dialogView.bridgedMode.isEnabled = false
|
||||||
|
dialogView.bridgedMode.isChecked = true
|
||||||
|
}
|
||||||
|
} else if (!dialogView.bridgedMode.isEnabled) {
|
||||||
|
dialogView.bridgedMode.isEnabled = true
|
||||||
|
dialogView.bridgedMode.isChecked = userBridgedMode
|
||||||
|
}
|
||||||
|
return auto + set > 0
|
||||||
|
}
|
||||||
private fun populateFromConfiguration() {
|
private fun populateFromConfiguration() {
|
||||||
dialogView.ssid.setText(base.ssid)
|
dialogView.ssid.setText(base.ssid)
|
||||||
if (!arg.p2pMode) dialogView.security.setSelection(base.securityType)
|
if (!arg.p2pMode) dialogView.security.setSelection(base.securityType)
|
||||||
@@ -208,10 +237,13 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
|
|||||||
dialogView.autoShutdown.isChecked = base.isAutoShutdownEnabled
|
dialogView.autoShutdown.isChecked = base.isAutoShutdownEnabled
|
||||||
dialogView.timeout.setText(base.shutdownTimeoutMillis.let { if (it == 0L) "" else it.toString() })
|
dialogView.timeout.setText(base.shutdownTimeoutMillis.let { if (it == 0L) "" else it.toString() })
|
||||||
if (Build.VERSION.SDK_INT >= 23 || arg.p2pMode) {
|
if (Build.VERSION.SDK_INT >= 23 || arg.p2pMode) {
|
||||||
val selection = if (base.channel != 0) {
|
dialogView.band2G.setSelection(locate(SoftApConfigurationCompat.BAND_2GHZ, channels2G))
|
||||||
bandOptions.indexOfFirst { it.channel == base.channel }
|
dialogView.band5G.setSelection(locate(SoftApConfigurationCompat.BAND_5GHZ, currentChannels5G))
|
||||||
} else bandOptions.indexOfFirst { it.band == base.band }
|
dialogView.band6G.setSelection(locate(SoftApConfigurationCompat.BAND_6GHZ, channels6G))
|
||||||
dialogView.band.setSelection(if (selection == -1) 0 else selection)
|
dialogView.band60G.setSelection(locate(SoftApConfigurationCompat.BAND_60GHZ, channels60G))
|
||||||
|
userBridgedMode = base.channels.size() > 1
|
||||||
|
dialogView.bridgedMode.isChecked = userBridgedMode
|
||||||
|
setBridgedMode()
|
||||||
}
|
}
|
||||||
dialogView.bssid.setText(base.bssid?.toString())
|
dialogView.bssid.setText(base.bssid?.toString())
|
||||||
dialogView.hiddenSsid.isChecked = base.isHiddenSsid
|
dialogView.hiddenSsid.isChecked = base.isHiddenSsid
|
||||||
@@ -219,6 +251,11 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
|
|||||||
dialogView.clientUserControl.isChecked = base.isClientControlByUserEnabled
|
dialogView.clientUserControl.isChecked = base.isClientControlByUserEnabled
|
||||||
dialogView.blockedList.setText(base.blockedClientList.joinToString("\n"))
|
dialogView.blockedList.setText(base.blockedClientList.joinToString("\n"))
|
||||||
dialogView.allowedList.setText(base.allowedClientList.joinToString("\n"))
|
dialogView.allowedList.setText(base.allowedClientList.joinToString("\n"))
|
||||||
|
dialogView.macRandomization.isChecked =
|
||||||
|
base.macRandomizationSetting == SoftApConfigurationCompat.RANDOMIZATION_PERSISTENT
|
||||||
|
dialogView.bridgedModeOpportunisticShutdown.isChecked = base.isBridgedModeOpportunisticShutdownEnabled
|
||||||
|
dialogView.ieee80211ax.isChecked = base.isIeee80211axEnabled
|
||||||
|
dialogView.userConfig.isChecked = base.isUserConfiguration
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
@@ -254,6 +291,29 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
dialogView.timeoutWrapper.error = timeoutError
|
dialogView.timeoutWrapper.error = timeoutError
|
||||||
|
val isBandValid = when {
|
||||||
|
arg.p2pMode || Build.VERSION.SDK_INT in 23 until 30 -> {
|
||||||
|
val option5G = dialogView.band5G.selectedItem
|
||||||
|
when (dialogView.band2G.selectedItem) {
|
||||||
|
is ChannelOption.Disabled -> option5G !is ChannelOption.Disabled &&
|
||||||
|
(!arg.p2pMode || RepeaterService.safeMode || option5G !is ChannelOption.Auto)
|
||||||
|
is ChannelOption.Auto ->
|
||||||
|
(arg.p2pMode || Build.VERSION.SDK_INT >= 28) && option5G is ChannelOption.Auto ||
|
||||||
|
(!arg.p2pMode || RepeaterService.safeMode) && option5G is ChannelOption.Disabled
|
||||||
|
else -> option5G is ChannelOption.Disabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Build.VERSION.SDK_INT == 30 -> {
|
||||||
|
var expected = 1
|
||||||
|
var set = 0
|
||||||
|
for (s in arrayOf(dialogView.band2G, dialogView.band5G, dialogView.band6G)) when (s.selectedItem) {
|
||||||
|
is ChannelOption.Auto -> expected = 0
|
||||||
|
!is ChannelOption.Disabled -> ++set
|
||||||
|
}
|
||||||
|
set == expected
|
||||||
|
}
|
||||||
|
else -> setBridgedMode()
|
||||||
|
}
|
||||||
dialogView.bssidWrapper.error = null
|
dialogView.bssidWrapper.error = null
|
||||||
val bssidValid = dialogView.bssid.length() == 0 || try {
|
val bssidValid = dialogView.bssid.length() == 0 || try {
|
||||||
MacAddressCompat.fromString(dialogView.bssid.text.toString())
|
MacAddressCompat.fromString(dialogView.bssid.text.toString())
|
||||||
@@ -290,7 +350,7 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
|
|||||||
val canCopy = timeoutError == null && bssidValid && maxClientError == null && blockedListError == null &&
|
val canCopy = timeoutError == null && bssidValid && maxClientError == null && blockedListError == null &&
|
||||||
allowedListError == null
|
allowedListError == null
|
||||||
(dialog as? AlertDialog)?.getButton(DialogInterface.BUTTON_POSITIVE)?.isEnabled =
|
(dialog as? AlertDialog)?.getButton(DialogInterface.BUTTON_POSITIVE)?.isEnabled =
|
||||||
ssidLength in 1..32 && passwordValid && canCopy
|
ssidLength in 1..32 && passwordValid && isBandValid && canCopy
|
||||||
dialogView.toolbar.menu.findItem(android.R.id.copy).isEnabled = canCopy
|
dialogView.toolbar.menu.findItem(android.R.id.copy).isEnabled = canCopy
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,6 +358,9 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
|
|||||||
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { }
|
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { }
|
||||||
override fun afterTextChanged(editable: Editable) = validate()
|
override fun afterTextChanged(editable: Editable) = validate()
|
||||||
|
|
||||||
|
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) = validate()
|
||||||
|
override fun onNothingSelected(parent: AdapterView<*>?) = error("unreachable")
|
||||||
|
|
||||||
override fun onMenuItemClick(item: MenuItem?): Boolean {
|
override fun onMenuItemClick(item: MenuItem?): Boolean {
|
||||||
return when (item?.itemId) {
|
return when (item?.itemId) {
|
||||||
android.R.id.copy -> {
|
android.R.id.copy -> {
|
||||||
@@ -318,7 +381,7 @@ class WifiApDialogFragment : AlertDialogFragment<WifiApDialogFragment.Arg, WifiA
|
|||||||
}
|
}
|
||||||
true
|
true
|
||||||
} catch (e: RuntimeException) {
|
} catch (e: RuntimeException) {
|
||||||
SmartSnackbar.make(e).show()
|
Toast.makeText(context, e.readableMessage, Toast.LENGTH_LONG).show()
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
R.id.share_qr -> {
|
R.id.share_qr -> {
|
||||||
|
|||||||
@@ -3,18 +3,16 @@ package be.mygod.vpnhotspot.net.wifi
|
|||||||
import android.annotation.TargetApi
|
import android.annotation.TargetApi
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.net.MacAddress
|
import android.content.res.Resources
|
||||||
import android.net.wifi.SoftApConfiguration
|
import android.net.wifi.SoftApConfiguration
|
||||||
import android.net.wifi.WifiManager
|
import android.net.wifi.WifiManager
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
|
import android.os.Parcelable
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import be.mygod.vpnhotspot.App.Companion.app
|
import be.mygod.vpnhotspot.App.Companion.app
|
||||||
import be.mygod.vpnhotspot.net.wifi.SoftApConfigurationCompat.Companion.toCompat
|
import be.mygod.vpnhotspot.net.wifi.SoftApConfigurationCompat.Companion.toCompat
|
||||||
import be.mygod.vpnhotspot.util.ConstantLookup
|
import be.mygod.vpnhotspot.util.*
|
||||||
import be.mygod.vpnhotspot.util.LongConstantLookup
|
|
||||||
import be.mygod.vpnhotspot.util.Services
|
|
||||||
import be.mygod.vpnhotspot.util.callSuper
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.lang.reflect.InvocationHandler
|
import java.lang.reflect.InvocationHandler
|
||||||
import java.lang.reflect.Method
|
import java.lang.reflect.Method
|
||||||
@@ -27,6 +25,8 @@ object WifiApManager {
|
|||||||
*/
|
*/
|
||||||
@RequiresApi(30)
|
@RequiresApi(30)
|
||||||
private const val ACTION_RESOURCES_APK = "com.android.server.wifi.intent.action.SERVICE_WIFI_RESOURCES_APK"
|
private const val ACTION_RESOURCES_APK = "com.android.server.wifi.intent.action.SERVICE_WIFI_RESOURCES_APK"
|
||||||
|
@RequiresApi(30)
|
||||||
|
const val RESOURCES_PACKAGE = "com.android.wifi.resources"
|
||||||
/**
|
/**
|
||||||
* Based on: https://android.googlesource.com/platform/frameworks/opt/net/wifi/+/000ad45/service/java/com/android/server/wifi/WifiContext.java#66
|
* Based on: https://android.googlesource.com/platform/frameworks/opt/net/wifi/+/000ad45/service/java/com/android/server/wifi/WifiContext.java#66
|
||||||
*/
|
*/
|
||||||
@@ -34,6 +34,115 @@ object WifiApManager {
|
|||||||
val resolvedActivity get() = app.packageManager.queryIntentActivities(Intent(ACTION_RESOURCES_APK),
|
val resolvedActivity get() = app.packageManager.queryIntentActivities(Intent(ACTION_RESOURCES_APK),
|
||||||
PackageManager.MATCH_SYSTEM_ONLY).single()
|
PackageManager.MATCH_SYSTEM_ONLY).single()
|
||||||
|
|
||||||
|
private const val CONFIG_P2P_MAC_RANDOMIZATION_SUPPORTED = "config_wifi_p2p_mac_randomization_supported"
|
||||||
|
val p2pMacRandomizationSupported get() = when (Build.VERSION.SDK_INT) {
|
||||||
|
29 -> Resources.getSystem().run {
|
||||||
|
getBoolean(getIdentifier(CONFIG_P2P_MAC_RANDOMIZATION_SUPPORTED, "bool", "android"))
|
||||||
|
}
|
||||||
|
in 30..Int.MAX_VALUE -> @TargetApi(30) {
|
||||||
|
val info = resolvedActivity.activityInfo
|
||||||
|
val resources = app.packageManager.getResourcesForApplication(info.applicationInfo)
|
||||||
|
resources.getBoolean(resources.findIdentifier(CONFIG_P2P_MAC_RANDOMIZATION_SUPPORTED, "bool",
|
||||||
|
RESOURCES_PACKAGE, info.packageName))
|
||||||
|
}
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
|
||||||
|
@get:RequiresApi(30)
|
||||||
|
private val apMacRandomizationSupported by lazy {
|
||||||
|
WifiManager::class.java.getDeclaredMethod("isApMacRandomizationSupported")
|
||||||
|
}
|
||||||
|
@get:RequiresApi(30)
|
||||||
|
val isApMacRandomizationSupported get() = apMacRandomizationSupported(Services.wifi) as Boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broadcast intent action indicating that Wi-Fi AP has been enabled, disabled,
|
||||||
|
* enabling, disabling, or failed.
|
||||||
|
*/
|
||||||
|
const val WIFI_AP_STATE_CHANGED_ACTION = "android.net.wifi.WIFI_AP_STATE_CHANGED"
|
||||||
|
/**
|
||||||
|
* The lookup key for an int that indicates whether Wi-Fi AP is enabled,
|
||||||
|
* disabled, enabling, disabling, or failed. Retrieve it with [Intent.getIntExtra].
|
||||||
|
*
|
||||||
|
* @see WIFI_AP_STATE_DISABLED
|
||||||
|
* @see WIFI_AP_STATE_DISABLING
|
||||||
|
* @see WIFI_AP_STATE_ENABLED
|
||||||
|
* @see WIFI_AP_STATE_ENABLING
|
||||||
|
* @see WIFI_AP_STATE_FAILED
|
||||||
|
*/
|
||||||
|
private const val EXTRA_WIFI_AP_STATE = "wifi_state"
|
||||||
|
/**
|
||||||
|
* An extra containing the int error code for Soft AP start failure.
|
||||||
|
* Can be obtained from the [WIFI_AP_STATE_CHANGED_ACTION] using [Intent.getIntExtra].
|
||||||
|
* This extra will only be attached if [EXTRA_WIFI_AP_STATE] is
|
||||||
|
* attached and is equal to [WIFI_AP_STATE_FAILED].
|
||||||
|
*
|
||||||
|
* The error code will be one of:
|
||||||
|
* {@link #SAP_START_FAILURE_GENERAL},
|
||||||
|
* {@link #SAP_START_FAILURE_NO_CHANNEL},
|
||||||
|
* {@link #SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION}
|
||||||
|
*
|
||||||
|
* 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"
|
||||||
|
/**
|
||||||
|
* The lookup key for a String extra that stores the interface name used for the Soft AP.
|
||||||
|
* This extra is included in the broadcast [WIFI_AP_STATE_CHANGED_ACTION].
|
||||||
|
* Retrieve its value with [Intent.getStringExtra].
|
||||||
|
*
|
||||||
|
* 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"
|
||||||
|
|
||||||
|
fun checkWifiApState(state: Int) = if (state < WIFI_AP_STATE_DISABLING || state > WIFI_AP_STATE_FAILED) {
|
||||||
|
Timber.w(Exception("Unknown state $state"))
|
||||||
|
false
|
||||||
|
} else true
|
||||||
|
val Intent.wifiApState get() =
|
||||||
|
getIntExtra(EXTRA_WIFI_AP_STATE, WIFI_AP_STATE_DISABLED).also { checkWifiApState(it) }
|
||||||
|
/**
|
||||||
|
* Wi-Fi AP is currently being disabled. The state will change to
|
||||||
|
* [WIFI_AP_STATE_DISABLED] if it finishes successfully.
|
||||||
|
*
|
||||||
|
* @see WIFI_AP_STATE_CHANGED_ACTION
|
||||||
|
* @see #getWifiApState()
|
||||||
|
*/
|
||||||
|
const val WIFI_AP_STATE_DISABLING = 10
|
||||||
|
/**
|
||||||
|
* Wi-Fi AP is disabled.
|
||||||
|
*
|
||||||
|
* @see WIFI_AP_STATE_CHANGED_ACTION
|
||||||
|
* @see #getWifiState()
|
||||||
|
*/
|
||||||
|
const val WIFI_AP_STATE_DISABLED = 11
|
||||||
|
/**
|
||||||
|
* Wi-Fi AP is currently being enabled. The state will change to
|
||||||
|
* {@link #WIFI_AP_STATE_ENABLED} if it finishes successfully.
|
||||||
|
*
|
||||||
|
* @see WIFI_AP_STATE_CHANGED_ACTION
|
||||||
|
* @see #getWifiApState()
|
||||||
|
*/
|
||||||
|
const val WIFI_AP_STATE_ENABLING = 12
|
||||||
|
/**
|
||||||
|
* Wi-Fi AP is enabled.
|
||||||
|
*
|
||||||
|
* @see WIFI_AP_STATE_CHANGED_ACTION
|
||||||
|
* @see #getWifiApState()
|
||||||
|
*/
|
||||||
|
const val WIFI_AP_STATE_ENABLED = 13
|
||||||
|
/**
|
||||||
|
* Wi-Fi AP is in a failed state. This state will occur when an error occurs during
|
||||||
|
* enabling or disabling
|
||||||
|
*
|
||||||
|
* @see WIFI_AP_STATE_CHANGED_ACTION
|
||||||
|
* @see #getWifiApState()
|
||||||
|
*/
|
||||||
|
const val WIFI_AP_STATE_FAILED = 14
|
||||||
|
|
||||||
private val getWifiApConfiguration by lazy { WifiManager::class.java.getDeclaredMethod("getWifiApConfiguration") }
|
private val getWifiApConfiguration by lazy { WifiManager::class.java.getDeclaredMethod("getWifiApConfiguration") }
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
private val setWifiApConfiguration by lazy {
|
private val setWifiApConfiguration by lazy {
|
||||||
@@ -63,40 +172,65 @@ object WifiApManager {
|
|||||||
/**
|
/**
|
||||||
* Called when soft AP state changes.
|
* Called when soft AP state changes.
|
||||||
*
|
*
|
||||||
* @param state new new AP state. One of {@link #WIFI_AP_STATE_DISABLED},
|
* @param state the new AP state. One of [WIFI_AP_STATE_DISABLED], [WIFI_AP_STATE_DISABLING],
|
||||||
* {@link #WIFI_AP_STATE_DISABLING}, {@link #WIFI_AP_STATE_ENABLED},
|
* [WIFI_AP_STATE_ENABLED], [WIFI_AP_STATE_ENABLING], [WIFI_AP_STATE_FAILED]
|
||||||
* {@link #WIFI_AP_STATE_ENABLING}, {@link #WIFI_AP_STATE_FAILED}
|
|
||||||
* @param failureReason reason when in failed state. One of
|
* @param failureReason reason when in failed state. One of
|
||||||
* {@link #SAP_START_FAILURE_GENERAL}, {@link #SAP_START_FAILURE_NO_CHANNEL}
|
* {@link #SAP_START_FAILURE_GENERAL},
|
||||||
|
* {@link #SAP_START_FAILURE_NO_CHANNEL},
|
||||||
|
* {@link #SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION}
|
||||||
*/
|
*/
|
||||||
fun onStateChanged(state: Int, failureReason: Int) { }
|
fun onStateChanged(state: Int, failureReason: Int) { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when number of connected clients to soft AP changes.
|
* Called when number of connected clients to soft AP changes.
|
||||||
*
|
*
|
||||||
|
* It is not recommended to use this legacy method on API 30+.
|
||||||
|
*
|
||||||
* @param numClients number of connected clients
|
* @param numClients number of connected clients
|
||||||
*/
|
*/
|
||||||
@Deprecated("onConnectedClientsChanged")
|
|
||||||
fun onNumClientsChanged(numClients: Int) { }
|
fun onNumClientsChanged(numClients: Int) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the connected clients to soft AP changes.
|
||||||
|
*
|
||||||
|
* @param clients the currently connected clients
|
||||||
|
*/
|
||||||
@RequiresApi(30)
|
@RequiresApi(30)
|
||||||
fun onConnectedClientsChanged(clients: List<MacAddress>) {
|
fun onConnectedClientsChanged(clients: List<Parcelable>) = onNumClientsChanged(clients.size)
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
onNumClientsChanged(clients.size)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when information of softap changes.
|
||||||
|
*
|
||||||
|
* @param info is the softap information. [SoftApInfo]
|
||||||
|
* At most one will be returned on API 30.
|
||||||
|
*/
|
||||||
@RequiresApi(30)
|
@RequiresApi(30)
|
||||||
fun onInfoChanged(frequency: Int, bandwidth: Int) { }
|
fun onInfoChanged(info: List<Parcelable>) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when capability of softap changes.
|
||||||
|
*
|
||||||
|
* @param capability is the softap capability. [SoftApCapability]
|
||||||
|
*/
|
||||||
@RequiresApi(30)
|
@RequiresApi(30)
|
||||||
fun onCapabilityChanged(maxSupportedClients: Int, supportedFeatures: Long) { }
|
fun onCapabilityChanged(capability: Parcelable) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when client trying to connect but device blocked the client with specific reason.
|
||||||
|
*
|
||||||
|
* Can be used to ask user to update client to allowed list or blocked list
|
||||||
|
* when reason is {@link SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER}, or
|
||||||
|
* indicate the block due to maximum supported client number limitation when reason is
|
||||||
|
* {@link SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS}.
|
||||||
|
*
|
||||||
|
* @param client the currently blocked client.
|
||||||
|
* @param blockedReason one of blocked reason from [SapClientBlockedReason]
|
||||||
|
*/
|
||||||
@RequiresApi(30)
|
@RequiresApi(30)
|
||||||
fun onBlockedClientConnecting(client: MacAddress, blockedReason: Int) { }
|
fun onBlockedClientConnecting(client: Parcelable, blockedReason: Int) { }
|
||||||
}
|
}
|
||||||
@RequiresApi(28)
|
@RequiresApi(23)
|
||||||
val failureReasonLookup = ConstantLookup<WifiManager>("SAP_START_FAILURE_",
|
val failureReasonLookup = ConstantLookup<WifiManager>("SAP_START_FAILURE_", "GENERAL", "NO_CHANNEL")
|
||||||
"SAP_START_FAILURE_GENERAL", "SAP_START_FAILURE_NO_CHANNEL")
|
|
||||||
@get:RequiresApi(30)
|
@get:RequiresApi(30)
|
||||||
val clientBlockLookup by lazy { ConstantLookup<WifiManager>("SAP_CLIENT_") }
|
val clientBlockLookup by lazy { ConstantLookup<WifiManager>("SAP_CLIENT_") }
|
||||||
|
|
||||||
@@ -111,25 +245,6 @@ object WifiApManager {
|
|||||||
WifiManager::class.java.getDeclaredMethod("unregisterSoftApCallback", interfaceSoftApCallback)
|
WifiManager::class.java.getDeclaredMethod("unregisterSoftApCallback", interfaceSoftApCallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val getMacAddress by lazy {
|
|
||||||
Class.forName("android.net.wifi.WifiClient").getDeclaredMethod("getMacAddress")
|
|
||||||
}
|
|
||||||
|
|
||||||
private val classSoftApInfo by lazy { Class.forName("android.net.wifi.SoftApInfo") }
|
|
||||||
private val getFrequency by lazy { classSoftApInfo.getDeclaredMethod("getFrequency") }
|
|
||||||
private val getBandwidth by lazy { classSoftApInfo.getDeclaredMethod("getBandwidth") }
|
|
||||||
@RequiresApi(30)
|
|
||||||
val channelWidthLookup = ConstantLookup("CHANNEL_WIDTH_") { classSoftApInfo }
|
|
||||||
const val CHANNEL_WIDTH_INVALID = 0
|
|
||||||
|
|
||||||
private val classSoftApCapability by lazy { Class.forName("android.net.wifi.SoftApCapability") }
|
|
||||||
private val getMaxSupportedClients by lazy { classSoftApCapability.getDeclaredMethod("getMaxSupportedClients") }
|
|
||||||
private val areFeaturesSupported by lazy {
|
|
||||||
classSoftApCapability.getDeclaredMethod("areFeaturesSupported", Long::class.java)
|
|
||||||
}
|
|
||||||
@get:RequiresApi(30)
|
|
||||||
val featureLookup by lazy { LongConstantLookup(classSoftApCapability, "SOFTAP_FEATURE_") }
|
|
||||||
|
|
||||||
@RequiresApi(28)
|
@RequiresApi(28)
|
||||||
fun registerSoftApCallback(callback: SoftApCallbackCompat, executor: Executor): Any {
|
fun registerSoftApCallback(callback: SoftApCallbackCompat, executor: Executor): Any {
|
||||||
val proxy = Proxy.newProxyInstance(interfaceSoftApCallback.classLoader,
|
val proxy = Proxy.newProxyInstance(interfaceSoftApCallback.classLoader,
|
||||||
@@ -141,49 +256,37 @@ object WifiApManager {
|
|||||||
} else invokeActual(proxy, method, args)
|
} else invokeActual(proxy, method, args)
|
||||||
|
|
||||||
private fun invokeActual(proxy: Any, method: Method, args: Array<out Any?>?): Any? {
|
private fun invokeActual(proxy: Any, method: Method, args: Array<out Any?>?): Any? {
|
||||||
val noArgs = args?.size ?: 0
|
return when {
|
||||||
return when (val name = method.name) {
|
method.matches("onStateChanged", Integer.TYPE, Integer.TYPE) -> {
|
||||||
"onStateChanged" -> {
|
|
||||||
if (noArgs != 2) Timber.w("Unexpected args for $name: ${args?.contentToString()}")
|
|
||||||
callback.onStateChanged(args!![0] as Int, args[1] as Int)
|
callback.onStateChanged(args!![0] as Int, args[1] as Int)
|
||||||
}
|
}
|
||||||
"onNumClientsChanged" -> @Suppress("DEPRECATION") {
|
method.matches("onNumClientsChanged", Integer.TYPE) -> @Suppress("DEPRECATION") {
|
||||||
if (Build.VERSION.SDK_INT >= 30) Timber.w(Exception("Unexpected onNumClientsChanged"))
|
if (Build.VERSION.SDK_INT >= 30) Timber.w(Exception("Unexpected onNumClientsChanged"))
|
||||||
if (noArgs != 1) Timber.w("Unexpected args for $name: ${args?.contentToString()}")
|
|
||||||
callback.onNumClientsChanged(args!![0] as Int)
|
callback.onNumClientsChanged(args!![0] as Int)
|
||||||
}
|
}
|
||||||
"onConnectedClientsChanged" -> @TargetApi(30) {
|
method.matches1<java.util.List<*>>("onConnectedClientsChanged") -> @TargetApi(30) {
|
||||||
if (Build.VERSION.SDK_INT < 30) Timber.w(Exception("Unexpected onConnectedClientsChanged"))
|
if (Build.VERSION.SDK_INT < 30) Timber.w(Exception("Unexpected onConnectedClientsChanged"))
|
||||||
if (noArgs != 1) Timber.w("Unexpected args for $name: ${args?.contentToString()}")
|
@Suppress("UNCHECKED_CAST")
|
||||||
callback.onConnectedClientsChanged((args!![0] as? Iterable<*> ?: return null)
|
callback.onConnectedClientsChanged(args!![0] as List<Parcelable>)
|
||||||
.map { getMacAddress(it) as MacAddress })
|
|
||||||
}
|
}
|
||||||
"onInfoChanged" -> @TargetApi(30) {
|
method.matches1<java.util.List<*>>("onInfoChanged") -> @TargetApi(31) {
|
||||||
if (Build.VERSION.SDK_INT < 30) Timber.w(Exception("Unexpected onInfoChanged"))
|
if (Build.VERSION.SDK_INT < 31) Timber.w(Exception("Unexpected onInfoChanged API 31+"))
|
||||||
if (noArgs != 1) Timber.w("Unexpected args for $name: ${args?.contentToString()}")
|
@Suppress("UNCHECKED_CAST")
|
||||||
val softApInfo = args!![0]
|
callback.onInfoChanged(args!![0] as List<Parcelable>)
|
||||||
if (softApInfo != null && classSoftApInfo.isAssignableFrom(softApInfo.javaClass)) {
|
|
||||||
callback.onInfoChanged(getFrequency(softApInfo) as Int, getBandwidth(softApInfo) as Int)
|
|
||||||
} else null
|
|
||||||
}
|
}
|
||||||
"onCapabilityChanged" -> @TargetApi(30) {
|
Build.VERSION.SDK_INT >= 30 && method.matches("onInfoChanged", SoftApInfo.clazz) -> {
|
||||||
if (Build.VERSION.SDK_INT < 30) Timber.w(Exception("Unexpected onCapabilityChanged"))
|
if (Build.VERSION.SDK_INT >= 31) return null // ignore old version calls
|
||||||
if (noArgs != 1) Timber.w("Unexpected args for $name: ${args?.contentToString()}")
|
val arg = args!![0]
|
||||||
val softApCapability = args!![0]
|
val info = SoftApInfo(arg as Parcelable)
|
||||||
var supportedFeatures = 0L
|
callback.onInfoChanged( // check for legacy empty info with CHANNEL_WIDTH_INVALID
|
||||||
var probe = 1L
|
if (info.frequency == 0 && info.bandwidth == 0) emptyList() else listOf(arg))
|
||||||
while (probe != 0L) {
|
|
||||||
if (areFeaturesSupported(softApCapability, probe) as Boolean) {
|
|
||||||
supportedFeatures = supportedFeatures or probe
|
|
||||||
}
|
|
||||||
probe += probe
|
|
||||||
}
|
|
||||||
callback.onCapabilityChanged(getMaxSupportedClients(softApCapability) as Int, supportedFeatures)
|
|
||||||
}
|
}
|
||||||
"onBlockedClientConnecting" -> @TargetApi(30) {
|
Build.VERSION.SDK_INT >= 30 && method.matches("onCapabilityChanged", SoftApCapability.clazz) -> {
|
||||||
if (Build.VERSION.SDK_INT < 30) Timber.w(Exception("Unexpected onBlockedClientConnecting"))
|
callback.onCapabilityChanged(args!![0] as Parcelable)
|
||||||
if (noArgs != 2) Timber.w("Unexpected args for $name: ${args?.contentToString()}")
|
}
|
||||||
callback.onBlockedClientConnecting(getMacAddress(args!![0]) as MacAddress, args[1] as Int)
|
Build.VERSION.SDK_INT >= 30 && method.matches("onBlockedClientConnecting", WifiClient.clazz,
|
||||||
|
Int::class.java) -> {
|
||||||
|
callback.onBlockedClientConnecting(args!![0] as Parcelable, args[1] as Int)
|
||||||
}
|
}
|
||||||
else -> callSuper(interfaceSoftApCallback, proxy, method, args)
|
else -> callSuper(interfaceSoftApCallback, proxy, method, args)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package be.mygod.vpnhotspot.net.wifi
|
||||||
|
|
||||||
|
import android.annotation.TargetApi
|
||||||
|
import android.net.MacAddress
|
||||||
|
import android.os.Parcelable
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
import be.mygod.vpnhotspot.util.UnblockCentral
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
@JvmInline
|
||||||
|
@RequiresApi(30)
|
||||||
|
value class WifiClient(val inner: Parcelable) {
|
||||||
|
companion object {
|
||||||
|
val clazz by lazy { Class.forName("android.net.wifi.WifiClient") }
|
||||||
|
private val getMacAddress by lazy { clazz.getDeclaredMethod("getMacAddress") }
|
||||||
|
@get:RequiresApi(31)
|
||||||
|
private val getApInstanceIdentifier by lazy @TargetApi(31) { UnblockCentral.getApInstanceIdentifier(clazz) }
|
||||||
|
}
|
||||||
|
|
||||||
|
val macAddress get() = getMacAddress(inner) as MacAddress
|
||||||
|
@get:RequiresApi(31)
|
||||||
|
val apInstanceIdentifier get() = try {
|
||||||
|
getApInstanceIdentifier(inner) as? String
|
||||||
|
} catch (e: ReflectiveOperationException) {
|
||||||
|
Timber.w(e)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,8 +9,8 @@ import androidx.annotation.RequiresApi
|
|||||||
import be.mygod.vpnhotspot.App.Companion.app
|
import be.mygod.vpnhotspot.App.Companion.app
|
||||||
import be.mygod.vpnhotspot.net.MacAddressCompat
|
import be.mygod.vpnhotspot.net.MacAddressCompat
|
||||||
import be.mygod.vpnhotspot.util.callSuper
|
import be.mygod.vpnhotspot.util.callSuper
|
||||||
|
import be.mygod.vpnhotspot.util.matchesCompat
|
||||||
import kotlinx.coroutines.CompletableDeferred
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
import timber.log.Timber
|
|
||||||
import java.lang.reflect.InvocationHandler
|
import java.lang.reflect.InvocationHandler
|
||||||
import java.lang.reflect.Method
|
import java.lang.reflect.Method
|
||||||
import java.lang.reflect.Proxy
|
import java.lang.reflect.Proxy
|
||||||
@@ -96,12 +96,11 @@ object WifiP2pManagerHelper {
|
|||||||
return result.future.await()
|
return result.future.await()
|
||||||
}
|
}
|
||||||
|
|
||||||
private val interfacePersistentGroupInfoListener by lazy @SuppressLint("PrivateApi") {
|
private val interfacePersistentGroupInfoListener by lazy {
|
||||||
Class.forName("android.net.wifi.p2p.WifiP2pManager\$PersistentGroupInfoListener")
|
Class.forName("android.net.wifi.p2p.WifiP2pManager\$PersistentGroupInfoListener")
|
||||||
}
|
}
|
||||||
private val getGroupList by lazy @SuppressLint("PrivateApi") {
|
private val classWifiP2pGroupList by lazy { Class.forName("android.net.wifi.p2p.WifiP2pGroupList") }
|
||||||
Class.forName("android.net.wifi.p2p.WifiP2pGroupList").getDeclaredMethod("getGroupList")
|
private val getGroupList by lazy { classWifiP2pGroupList.getDeclaredMethod("getGroupList") }
|
||||||
}
|
|
||||||
private val requestPersistentGroupInfo by lazy {
|
private val requestPersistentGroupInfo by lazy {
|
||||||
WifiP2pManager::class.java.getDeclaredMethod("requestPersistentGroupInfo",
|
WifiP2pManager::class.java.getDeclaredMethod("requestPersistentGroupInfo",
|
||||||
WifiP2pManager.Channel::class.java, interfacePersistentGroupInfoListener)
|
WifiP2pManager.Channel::class.java, interfacePersistentGroupInfoListener)
|
||||||
@@ -112,15 +111,13 @@ object WifiP2pManagerHelper {
|
|||||||
* Requires one of NETWORK_SETTING, NETWORK_STACK, or READ_WIFI_CREDENTIAL permission since API 30.
|
* Requires one of NETWORK_SETTING, NETWORK_STACK, or READ_WIFI_CREDENTIAL permission since API 30.
|
||||||
*
|
*
|
||||||
* @param c is the channel created at {@link #initialize}
|
* @param c is the channel created at {@link #initialize}
|
||||||
* @param listener for callback when persistent group info list is available. Can be null.
|
|
||||||
*/
|
*/
|
||||||
suspend fun WifiP2pManager.requestPersistentGroupInfo(c: WifiP2pManager.Channel): Collection<WifiP2pGroup> {
|
suspend fun WifiP2pManager.requestPersistentGroupInfo(c: WifiP2pManager.Channel): Collection<WifiP2pGroup> {
|
||||||
val result = CompletableDeferred<Collection<WifiP2pGroup>>()
|
val result = CompletableDeferred<Collection<WifiP2pGroup>>()
|
||||||
requestPersistentGroupInfo(this, c, Proxy.newProxyInstance(interfacePersistentGroupInfoListener.classLoader,
|
requestPersistentGroupInfo(this, c, Proxy.newProxyInstance(interfacePersistentGroupInfoListener.classLoader,
|
||||||
arrayOf(interfacePersistentGroupInfoListener), object : InvocationHandler {
|
arrayOf(interfacePersistentGroupInfoListener), object : InvocationHandler {
|
||||||
override fun invoke(proxy: Any, method: Method, args: Array<out Any?>?): Any? = when (method.name) {
|
override fun invoke(proxy: Any, method: Method, args: Array<out Any?>?): Any? = when {
|
||||||
"onPersistentGroupInfoAvailable" -> {
|
method.matchesCompat("onPersistentGroupInfoAvailable", args, classWifiP2pGroupList) -> {
|
||||||
if (args?.size != 1) Timber.w(IllegalArgumentException("Unexpected args: $args"))
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
result.complete(getGroupList(args!![0]) as Collection<WifiP2pGroup>)
|
result.complete(getGroupList(args!![0]) as Collection<WifiP2pGroup>)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
package be.mygod.vpnhotspot.preference
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.widget.ArrayAdapter
|
|
||||||
import androidx.core.os.bundleOf
|
|
||||||
import androidx.preference.EditTextPreferenceDialogFragmentCompat
|
|
||||||
import be.mygod.vpnhotspot.R
|
|
||||||
import be.mygod.vpnhotspot.widget.AlwaysAutoCompleteEditText
|
|
||||||
|
|
||||||
class AlwaysAutoCompleteEditTextPreferenceDialogFragment : EditTextPreferenceDialogFragmentCompat() {
|
|
||||||
companion object {
|
|
||||||
private const val ARG_SUGGESTIONS = "suggestions"
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setArguments(key: String, suggestions: Array<String>) {
|
|
||||||
arguments = bundleOf(ARG_KEY to key, ARG_SUGGESTIONS to suggestions)
|
|
||||||
}
|
|
||||||
|
|
||||||
private lateinit var editText: AlwaysAutoCompleteEditText
|
|
||||||
|
|
||||||
override fun onCreateDialogView(context: Context) = super.onCreateDialogView(context).apply {
|
|
||||||
editText = AlwaysAutoCompleteEditText(context).apply {
|
|
||||||
id = android.R.id.edit
|
|
||||||
minHeight = resources.getDimensionPixelSize(R.dimen.touch_target_min)
|
|
||||||
}
|
|
||||||
val oldEditText = findViewById<View>(android.R.id.edit)!!
|
|
||||||
val container = oldEditText.parent as ViewGroup
|
|
||||||
container.removeView(oldEditText)
|
|
||||||
container.addView(editText, oldEditText.layoutParams)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBindDialogView(view: View) {
|
|
||||||
super.onBindDialogView(view)
|
|
||||||
editText.hint = (preference.summaryProvider as SummaryFallbackProvider).fallback
|
|
||||||
arguments?.getStringArray(ARG_SUGGESTIONS)?.let { suggestions ->
|
|
||||||
editText.setAdapter(ArrayAdapter(view.context, android.R.layout.select_dialog_item, suggestions))
|
|
||||||
}
|
|
||||||
editText.clearFocus() // having focus is buggy currently
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package be.mygod.vpnhotspot.preference
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.ConnectivityManager
|
||||||
|
import android.net.LinkProperties
|
||||||
|
import android.net.Network
|
||||||
|
import android.net.NetworkCapabilities
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ArrayAdapter
|
||||||
|
import androidx.core.os.bundleOf
|
||||||
|
import androidx.preference.EditTextPreferenceDialogFragmentCompat
|
||||||
|
import be.mygod.vpnhotspot.R
|
||||||
|
import be.mygod.vpnhotspot.util.Services
|
||||||
|
import be.mygod.vpnhotspot.util.allInterfaceNames
|
||||||
|
import be.mygod.vpnhotspot.util.globalNetworkRequestBuilder
|
||||||
|
import be.mygod.vpnhotspot.widget.AlwaysAutoCompleteEditText
|
||||||
|
|
||||||
|
class AutoCompleteNetworkPreferenceDialogFragment : EditTextPreferenceDialogFragmentCompat() {
|
||||||
|
fun setArguments(key: String) {
|
||||||
|
arguments = bundleOf(ARG_KEY to key)
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var editText: AlwaysAutoCompleteEditText
|
||||||
|
private lateinit var adapter: ArrayAdapter<String>
|
||||||
|
private fun updateAdapter() {
|
||||||
|
adapter.clear()
|
||||||
|
adapter.addAll(interfaceNames.flatMap { it.value })
|
||||||
|
}
|
||||||
|
|
||||||
|
private val interfaceNames = mutableMapOf<Network, List<String>>()
|
||||||
|
private val callback = object : ConnectivityManager.NetworkCallback() {
|
||||||
|
override fun onLinkPropertiesChanged(network: Network, properties: LinkProperties) {
|
||||||
|
interfaceNames[network] = properties.allInterfaceNames
|
||||||
|
updateAdapter()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLost(network: Network) {
|
||||||
|
interfaceNames.remove(network)
|
||||||
|
updateAdapter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateDialogView(context: Context) = super.onCreateDialogView(context).apply {
|
||||||
|
editText = AlwaysAutoCompleteEditText(context).apply {
|
||||||
|
id = android.R.id.edit
|
||||||
|
minHeight = resources.getDimensionPixelSize(R.dimen.touch_target_min)
|
||||||
|
}
|
||||||
|
val oldEditText = findViewById<View>(android.R.id.edit)!!
|
||||||
|
val container = oldEditText.parent as ViewGroup
|
||||||
|
container.removeView(oldEditText)
|
||||||
|
container.addView(editText, oldEditText.layoutParams)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindDialogView(view: View) {
|
||||||
|
super.onBindDialogView(view)
|
||||||
|
editText.hint = (preference.summaryProvider as SummaryFallbackProvider).fallback
|
||||||
|
adapter = ArrayAdapter(view.context, android.R.layout.select_dialog_item)
|
||||||
|
editText.setAdapter(adapter)
|
||||||
|
editText.clearFocus() // having focus is buggy currently
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStart() {
|
||||||
|
super.onStart()
|
||||||
|
Services.connectivity.registerNetworkCallback(globalNetworkRequestBuilder().apply {
|
||||||
|
removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
|
||||||
|
removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
|
||||||
|
removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
|
||||||
|
}.build(), callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStop() {
|
||||||
|
Services.connectivity.unregisterNetworkCallback(callback)
|
||||||
|
interfaceNames.clear()
|
||||||
|
updateAdapter()
|
||||||
|
super.onStop()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,8 +13,8 @@ import androidx.preference.Preference
|
|||||||
import be.mygod.vpnhotspot.R
|
import be.mygod.vpnhotspot.R
|
||||||
import be.mygod.vpnhotspot.net.monitor.FallbackUpstreamMonitor
|
import be.mygod.vpnhotspot.net.monitor.FallbackUpstreamMonitor
|
||||||
import be.mygod.vpnhotspot.net.monitor.UpstreamMonitor
|
import be.mygod.vpnhotspot.net.monitor.UpstreamMonitor
|
||||||
import be.mygod.vpnhotspot.util.SpanFormatter
|
|
||||||
import be.mygod.vpnhotspot.util.allRoutes
|
import be.mygod.vpnhotspot.util.allRoutes
|
||||||
|
import be.mygod.vpnhotspot.util.format
|
||||||
import be.mygod.vpnhotspot.util.parseNumericAddress
|
import be.mygod.vpnhotspot.util.parseNumericAddress
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
@@ -73,7 +73,7 @@ class UpstreamsPreference(context: Context, attrs: AttributeSet) : Preference(co
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun onUpdate() = (context as LifecycleOwner).lifecycleScope.launchWhenStarted {
|
private fun onUpdate() = (context as LifecycleOwner).lifecycleScope.launchWhenStarted {
|
||||||
summary = SpanFormatter.format(context.getText(R.string.settings_service_upstream_monitor_summary),
|
summary = context.getText(R.string.settings_service_upstream_monitor_summary).format(
|
||||||
primary.charSequence, fallback.charSequence)
|
context.resources.configuration.locale, primary.charSequence, fallback.charSequence)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,8 +102,8 @@ class ProcessListener(private val terminateRegex: Regex,
|
|||||||
try {
|
try {
|
||||||
launch(parent) {
|
launch(parent) {
|
||||||
try {
|
try {
|
||||||
process.inputStream.bufferedReader().useLines {
|
process.inputStream.bufferedReader().useLines { lines ->
|
||||||
for (line in it) {
|
for (line in lines) {
|
||||||
trySend(ProcessData.StdoutLine(line)).onClosed { return@useLines }.onFailure { throw it!! }
|
trySend(ProcessData.StdoutLine(line)).onClosed { return@useLines }.onFailure { throw it!! }
|
||||||
if (terminateRegex.containsMatchIn(line)) process.destroy()
|
if (terminateRegex.containsMatchIn(line)) process.destroy()
|
||||||
}
|
}
|
||||||
@@ -112,8 +112,8 @@ class ProcessListener(private val terminateRegex: Regex,
|
|||||||
}
|
}
|
||||||
launch(parent) {
|
launch(parent) {
|
||||||
try {
|
try {
|
||||||
process.errorStream.bufferedReader().useLines {
|
process.errorStream.bufferedReader().useLines { lines ->
|
||||||
for (line in it) trySend(ProcessData.StdoutLine(line)).onClosed {
|
for (line in lines) trySend(ProcessData.StdoutLine(line)).onClosed {
|
||||||
return@useLines
|
return@useLines
|
||||||
}.onFailure { throw it!! }
|
}.onFailure { throw it!! }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package be.mygod.vpnhotspot.root
|
package be.mygod.vpnhotspot.root
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import be.mygod.librootkotlinx.*
|
import be.mygod.librootkotlinx.*
|
||||||
@@ -13,6 +14,7 @@ object RootManager : RootSession(), Logger {
|
|||||||
class RootInit : RootCommandNoResult {
|
class RootInit : RootCommandNoResult {
|
||||||
override suspend fun execute(): Parcelable? {
|
override suspend fun execute(): Parcelable? {
|
||||||
Timber.plant(object : Timber.DebugTree() {
|
Timber.plant(object : Timber.DebugTree() {
|
||||||
|
@SuppressLint("LogNotTimber")
|
||||||
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
|
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
|
||||||
if (priority >= Log.WARN) {
|
if (priority >= Log.WARN) {
|
||||||
System.err.println("$priority/$tag: $message")
|
System.err.println("$priority/$tag: $message")
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package be.mygod.vpnhotspot.root
|
package be.mygod.vpnhotspot.root
|
||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import android.util.Log
|
|
||||||
import be.mygod.librootkotlinx.RootCommand
|
import be.mygod.librootkotlinx.RootCommand
|
||||||
import be.mygod.librootkotlinx.RootCommandOneWay
|
import be.mygod.librootkotlinx.RootCommandOneWay
|
||||||
import be.mygod.vpnhotspot.net.Routing
|
import be.mygod.vpnhotspot.net.Routing
|
||||||
@@ -10,6 +9,7 @@ import kotlinx.coroutines.async
|
|||||||
import kotlinx.coroutines.coroutineScope
|
import kotlinx.coroutines.coroutineScope
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
object RoutingCommands {
|
object RoutingCommands {
|
||||||
@Parcelize
|
@Parcelize
|
||||||
@@ -20,7 +20,7 @@ object RoutingCommands {
|
|||||||
process.outputStream.bufferedWriter().use(Routing.Companion::appendCleanCommands)
|
process.outputStream.bufferedWriter().use(Routing.Companion::appendCleanCommands)
|
||||||
when (val code = process.waitFor()) {
|
when (val code = process.waitFor()) {
|
||||||
0 -> { }
|
0 -> { }
|
||||||
else -> Log.d("RoutingCommands.Clean", "Unexpected exit code $code")
|
else -> Timber.w("Unexpected exit code $code")
|
||||||
}
|
}
|
||||||
check(process.waitFor() == 0)
|
check(process.waitFor() == 0)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
package be.mygod.vpnhotspot.root
|
package be.mygod.vpnhotspot.root
|
||||||
|
|
||||||
import android.net.MacAddress
|
import android.annotation.TargetApi
|
||||||
|
import android.content.ClipData
|
||||||
|
import android.os.Build
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import be.mygod.librootkotlinx.ParcelableBoolean
|
import be.mygod.librootkotlinx.ParcelableBoolean
|
||||||
import be.mygod.librootkotlinx.RootCommand
|
import be.mygod.librootkotlinx.RootCommand
|
||||||
import be.mygod.librootkotlinx.RootCommandChannel
|
import be.mygod.librootkotlinx.RootCommandChannel
|
||||||
|
import be.mygod.vpnhotspot.App.Companion.app
|
||||||
|
import be.mygod.vpnhotspot.R
|
||||||
import be.mygod.vpnhotspot.net.wifi.SoftApConfigurationCompat
|
import be.mygod.vpnhotspot.net.wifi.SoftApConfigurationCompat
|
||||||
import be.mygod.vpnhotspot.net.wifi.WifiApManager
|
import be.mygod.vpnhotspot.net.wifi.WifiApManager
|
||||||
|
import be.mygod.vpnhotspot.net.wifi.WifiClient
|
||||||
import be.mygod.vpnhotspot.widget.SmartSnackbar
|
import be.mygod.vpnhotspot.widget.SmartSnackbar
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.channels.*
|
import kotlinx.coroutines.channels.*
|
||||||
@@ -26,32 +31,29 @@ object WifiApCommands {
|
|||||||
}
|
}
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class OnNumClientsChanged(val numClients: Int) : SoftApCallbackParcel() {
|
data class OnNumClientsChanged(val numClients: Int) : SoftApCallbackParcel() {
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
override fun dispatch(callback: WifiApManager.SoftApCallbackCompat) =
|
override fun dispatch(callback: WifiApManager.SoftApCallbackCompat) =
|
||||||
callback.onNumClientsChanged(numClients)
|
callback.onNumClientsChanged(numClients)
|
||||||
}
|
}
|
||||||
@Parcelize
|
@Parcelize
|
||||||
@RequiresApi(30)
|
@RequiresApi(30)
|
||||||
data class OnConnectedClientsChanged(val clients: List<MacAddress>) : SoftApCallbackParcel() {
|
data class OnConnectedClientsChanged(val clients: List<Parcelable>) : SoftApCallbackParcel() {
|
||||||
override fun dispatch(callback: WifiApManager.SoftApCallbackCompat) =
|
override fun dispatch(callback: WifiApManager.SoftApCallbackCompat) =
|
||||||
callback.onConnectedClientsChanged(clients)
|
callback.onConnectedClientsChanged(clients)
|
||||||
}
|
}
|
||||||
@Parcelize
|
@Parcelize
|
||||||
@RequiresApi(30)
|
@RequiresApi(30)
|
||||||
data class OnInfoChanged(val frequency: Int, val bandwidth: Int) : SoftApCallbackParcel() {
|
data class OnInfoChanged(val info: List<Parcelable>) : SoftApCallbackParcel() {
|
||||||
override fun dispatch(callback: WifiApManager.SoftApCallbackCompat) =
|
override fun dispatch(callback: WifiApManager.SoftApCallbackCompat) = callback.onInfoChanged(info)
|
||||||
callback.onInfoChanged(frequency, bandwidth)
|
|
||||||
}
|
}
|
||||||
@Parcelize
|
@Parcelize
|
||||||
@RequiresApi(30)
|
@RequiresApi(30)
|
||||||
data class OnCapabilityChanged(val maxSupportedClients: Int,
|
data class OnCapabilityChanged(val capability: Parcelable) : SoftApCallbackParcel() {
|
||||||
val supportedFeatures: Long) : SoftApCallbackParcel() {
|
|
||||||
override fun dispatch(callback: WifiApManager.SoftApCallbackCompat) =
|
override fun dispatch(callback: WifiApManager.SoftApCallbackCompat) =
|
||||||
callback.onCapabilityChanged(maxSupportedClients, supportedFeatures)
|
callback.onCapabilityChanged(capability)
|
||||||
}
|
}
|
||||||
@Parcelize
|
@Parcelize
|
||||||
@RequiresApi(30)
|
@RequiresApi(30)
|
||||||
data class OnBlockedClientConnecting(val client: MacAddress, val blockedReason: Int) : SoftApCallbackParcel() {
|
data class OnBlockedClientConnecting(val client: Parcelable, val blockedReason: Int) : SoftApCallbackParcel() {
|
||||||
override fun dispatch(callback: WifiApManager.SoftApCallbackCompat) =
|
override fun dispatch(callback: WifiApManager.SoftApCallbackCompat) =
|
||||||
callback.onBlockedClientConnecting(client, blockedReason)
|
callback.onBlockedClientConnecting(client, blockedReason)
|
||||||
}
|
}
|
||||||
@@ -60,7 +62,7 @@ object WifiApCommands {
|
|||||||
@Parcelize
|
@Parcelize
|
||||||
@RequiresApi(28)
|
@RequiresApi(28)
|
||||||
class RegisterSoftApCallback : RootCommandChannel<SoftApCallbackParcel> {
|
class RegisterSoftApCallback : RootCommandChannel<SoftApCallbackParcel> {
|
||||||
override fun create(scope: CoroutineScope) = scope.produce<SoftApCallbackParcel>(capacity = capacity) {
|
override fun create(scope: CoroutineScope) = scope.produce(capacity = capacity) {
|
||||||
val finish = CompletableDeferred<Unit>()
|
val finish = CompletableDeferred<Unit>()
|
||||||
val key = WifiApManager.registerSoftApCallback(object : WifiApManager.SoftApCallbackCompat {
|
val key = WifiApManager.registerSoftApCallback(object : WifiApManager.SoftApCallbackCompat {
|
||||||
private fun push(parcel: SoftApCallbackParcel) {
|
private fun push(parcel: SoftApCallbackParcel) {
|
||||||
@@ -71,20 +73,18 @@ object WifiApCommands {
|
|||||||
|
|
||||||
override fun onStateChanged(state: Int, failureReason: Int) =
|
override fun onStateChanged(state: Int, failureReason: Int) =
|
||||||
push(SoftApCallbackParcel.OnStateChanged(state, failureReason))
|
push(SoftApCallbackParcel.OnStateChanged(state, failureReason))
|
||||||
@Suppress("OverridingDeprecatedMember")
|
|
||||||
override fun onNumClientsChanged(numClients: Int) =
|
override fun onNumClientsChanged(numClients: Int) =
|
||||||
push(SoftApCallbackParcel.OnNumClientsChanged(numClients))
|
push(SoftApCallbackParcel.OnNumClientsChanged(numClients))
|
||||||
@RequiresApi(30)
|
@RequiresApi(30)
|
||||||
override fun onConnectedClientsChanged(clients: List<MacAddress>) =
|
override fun onConnectedClientsChanged(clients: List<Parcelable>) =
|
||||||
push(SoftApCallbackParcel.OnConnectedClientsChanged(clients))
|
push(SoftApCallbackParcel.OnConnectedClientsChanged(clients))
|
||||||
@RequiresApi(30)
|
@RequiresApi(30)
|
||||||
override fun onInfoChanged(frequency: Int, bandwidth: Int) =
|
override fun onInfoChanged(info: List<Parcelable>) = push(SoftApCallbackParcel.OnInfoChanged(info))
|
||||||
push(SoftApCallbackParcel.OnInfoChanged(frequency, bandwidth))
|
|
||||||
@RequiresApi(30)
|
@RequiresApi(30)
|
||||||
override fun onCapabilityChanged(maxSupportedClients: Int, supportedFeatures: Long) =
|
override fun onCapabilityChanged(capability: Parcelable) =
|
||||||
push(SoftApCallbackParcel.OnCapabilityChanged(maxSupportedClients, supportedFeatures))
|
push(SoftApCallbackParcel.OnCapabilityChanged(capability))
|
||||||
@RequiresApi(30)
|
@RequiresApi(30)
|
||||||
override fun onBlockedClientConnecting(client: MacAddress, blockedReason: Int) =
|
override fun onBlockedClientConnecting(client: Parcelable, blockedReason: Int) =
|
||||||
push(SoftApCallbackParcel.OnBlockedClientConnecting(client, blockedReason))
|
push(SoftApCallbackParcel.OnBlockedClientConnecting(client, blockedReason))
|
||||||
}) {
|
}) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
@@ -125,8 +125,21 @@ object WifiApCommands {
|
|||||||
}
|
}
|
||||||
is SoftApCallbackParcel.OnInfoChanged -> synchronized(callbacks) { lastCallback.info = parcel }
|
is SoftApCallbackParcel.OnInfoChanged -> synchronized(callbacks) { lastCallback.info = parcel }
|
||||||
is SoftApCallbackParcel.OnCapabilityChanged -> synchronized(callbacks) { lastCallback.capability = parcel }
|
is SoftApCallbackParcel.OnCapabilityChanged -> synchronized(callbacks) { lastCallback.capability = parcel }
|
||||||
|
is SoftApCallbackParcel.OnBlockedClientConnecting -> @TargetApi(30) { // passively consume events
|
||||||
|
val client = WifiClient(parcel.client)
|
||||||
|
val macAddress = client.macAddress
|
||||||
|
var name = macAddress.toString()
|
||||||
|
if (Build.VERSION.SDK_INT >= 31) client.apInstanceIdentifier?.let { name += "%$it" }
|
||||||
|
val reason = WifiApManager.clientBlockLookup(parcel.blockedReason, true)
|
||||||
|
Timber.i("$name blocked from connecting: $reason (${parcel.blockedReason})")
|
||||||
|
SmartSnackbar.make(app.getString(R.string.tethering_manage_wifi_client_blocked, name, reason)).apply {
|
||||||
|
action(R.string.tethering_manage_wifi_copy_mac) {
|
||||||
|
app.clipboard.setPrimaryClip(ClipData.newPlainText(null, macAddress.toString()))
|
||||||
|
}
|
||||||
|
}.show()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for (callback in synchronized(callbacks) { callbacks }) parcel.dispatch(callback)
|
for (callback in synchronized(callbacks) { callbacks.toList() }) parcel.dispatch(callback)
|
||||||
}
|
}
|
||||||
@RequiresApi(28)
|
@RequiresApi(28)
|
||||||
fun registerSoftApCallback(callback: WifiApManager.SoftApCallbackCompat) = synchronized(callbacks) {
|
fun registerSoftApCallback(callback: WifiApManager.SoftApCallbackCompat) = synchronized(callbacks) {
|
||||||
@@ -142,8 +155,8 @@ object WifiApCommands {
|
|||||||
SmartSnackbar.make(e).show()
|
SmartSnackbar.make(e).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lastCallback
|
null
|
||||||
} else null
|
} else lastCallback
|
||||||
}?.toSequence()?.forEach { it?.dispatch(callback) }
|
}?.toSequence()?.forEach { it?.dispatch(callback) }
|
||||||
@RequiresApi(28)
|
@RequiresApi(28)
|
||||||
fun unregisterSoftApCallback(callback: WifiApManager.SoftApCallbackCompat) = synchronized(callbacks) {
|
fun unregisterSoftApCallback(callback: WifiApManager.SoftApCallbackCompat) = synchronized(callbacks) {
|
||||||
|
|||||||
@@ -7,12 +7,12 @@ import be.mygod.vpnhotspot.App.Companion.app
|
|||||||
import be.mygod.vpnhotspot.R
|
import be.mygod.vpnhotspot.R
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
class ConstantLookup(private val prefix: String, private val lookup29: Array<out String>,
|
class ConstantLookup(private val prefix: String, private val lookup29: Array<out String?>,
|
||||||
private val clazz: () -> Class<*>) {
|
private val clazz: () -> Class<*>) {
|
||||||
private val lookup by lazy {
|
private val lookup by lazy {
|
||||||
SparseArrayCompat<String>().apply {
|
SparseArrayCompat<String>().apply {
|
||||||
for (field in clazz().declaredFields) try {
|
for (field in clazz().declaredFields) try {
|
||||||
if (field.name.startsWith(prefix)) put(field.getInt(null), field.name)
|
if (field.type == Int::class.java && field.name.startsWith(prefix)) put(field.getInt(null), field.name)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Timber.w(e)
|
Timber.w(e)
|
||||||
}
|
}
|
||||||
@@ -25,22 +25,22 @@ class ConstantLookup(private val prefix: String, private val lookup29: Array<out
|
|||||||
} catch (e: ReflectiveOperationException) {
|
} catch (e: ReflectiveOperationException) {
|
||||||
Timber.w(e)
|
Timber.w(e)
|
||||||
}
|
}
|
||||||
return lookup29.getOrNull(reason)?.let { if (trimPrefix) it.substring(prefix.length) else it }
|
return lookup29.getOrNull(reason)?.let { if (trimPrefix) it else prefix + it }
|
||||||
?: app.getString(R.string.failure_reason_unknown, reason)
|
?: app.getString(R.string.failure_reason_unknown, reason)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("FunctionName")
|
@Suppress("FunctionName")
|
||||||
fun ConstantLookup(prefix: String, vararg lookup29: String, clazz: () -> Class<*>) =
|
fun ConstantLookup(prefix: String, vararg lookup29: String?, clazz: () -> Class<*>) =
|
||||||
ConstantLookup(prefix, lookup29, clazz)
|
ConstantLookup(prefix, lookup29, clazz)
|
||||||
@Suppress("FunctionName")
|
@Suppress("FunctionName")
|
||||||
inline fun <reified T> ConstantLookup(prefix: String, vararg lookup29: String) =
|
inline fun <reified T> ConstantLookup(prefix: String, vararg lookup29: String?) =
|
||||||
ConstantLookup(prefix, lookup29) { T::class.java }
|
ConstantLookup(prefix, lookup29) { T::class.java }
|
||||||
|
|
||||||
class LongConstantLookup(private val clazz: Class<*>, private val prefix: String) {
|
class LongConstantLookup(private val clazz: Class<*>, private val prefix: String) {
|
||||||
private val lookup = LongSparseArray<String>().apply {
|
private val lookup = LongSparseArray<String>().apply {
|
||||||
for (field in clazz.declaredFields) try {
|
for (field in clazz.declaredFields) try {
|
||||||
if (field.name.startsWith(prefix)) put(field.getLong(null), field.name)
|
if (field.type == Long::class.java && field.name.startsWith(prefix)) put(field.getLong(null), field.name)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Timber.w(e)
|
Timber.w(e)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
package be.mygod.vpnhotspot.util
|
package be.mygod.vpnhotspot.util
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.ConnectivityManager
|
import android.net.ConnectivityManager
|
||||||
import android.net.wifi.WifiManager
|
import android.net.wifi.WifiManager
|
||||||
import android.net.wifi.p2p.WifiP2pManager
|
import android.net.wifi.p2p.WifiP2pManager
|
||||||
import android.util.Log
|
|
||||||
import androidx.core.content.getSystemService
|
import androidx.core.content.getSystemService
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
@SuppressLint("LogNotTimber")
|
|
||||||
object Services {
|
object Services {
|
||||||
private lateinit var contextInit: () -> Context
|
private lateinit var contextInit: () -> Context
|
||||||
val context by lazy { contextInit() }
|
val context by lazy { contextInit() }
|
||||||
@@ -22,7 +19,7 @@ object Services {
|
|||||||
try {
|
try {
|
||||||
context.getSystemService<WifiP2pManager>()
|
context.getSystemService<WifiP2pManager>()
|
||||||
} catch (e: RuntimeException) {
|
} catch (e: RuntimeException) {
|
||||||
if (android.os.Process.myUid() == 0) Log.w("WifiP2pManager", e) else Timber.w(e)
|
Timber.w(e)
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,84 +0,0 @@
|
|||||||
package be.mygod.vpnhotspot.util
|
|
||||||
|
|
||||||
import android.text.Spannable
|
|
||||||
import android.text.SpannableStringBuilder
|
|
||||||
import android.text.Spanned
|
|
||||||
import android.text.SpannedString
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides [String.format] style functions that work with [Spanned] strings and preserve formatting.
|
|
||||||
*
|
|
||||||
* https://github.com/george-steel/android-utils/blob/289aff11e53593a55d780f9f5986e49343a79e55/src/org/oshkimaadziig/george/androidutils/SpanFormatter.java
|
|
||||||
*
|
|
||||||
* @author George T. Steel
|
|
||||||
*/
|
|
||||||
object SpanFormatter {
|
|
||||||
private val formatSequence = "%([0-9]+\\$|<?)([^a-zA-z%]*)([[a-zA-Z%]&&[^tT]]|[tT][a-zA-Z])".toPattern()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Version of [String.format] that works on [Spanned] strings to preserve rich text formatting.
|
|
||||||
* Both the `format` as well as any `%s args` can be Spanned and will have their formatting preserved.
|
|
||||||
* Due to the way [Spannable]s work, any argument's spans will can only be included **once** in the result.
|
|
||||||
* Any duplicates will appear as text only.
|
|
||||||
*
|
|
||||||
* @param format the format string (see [java.util.Formatter.format])
|
|
||||||
* @param args
|
|
||||||
* the list of arguments passed to the formatter. If there are
|
|
||||||
* more arguments than required by `format`,
|
|
||||||
* additional arguments are ignored.
|
|
||||||
* @return the formatted string (with spans).
|
|
||||||
*/
|
|
||||||
fun format(format: CharSequence, vararg args: Any) = format(Locale.getDefault(), format, *args)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Version of [String.format] that works on [Spanned] strings to preserve rich text formatting.
|
|
||||||
* Both the `format` as well as any `%s args` can be Spanned and will have their formatting preserved.
|
|
||||||
* Due to the way [Spannable]s work, any argument's spans will can only be included **once** in the result.
|
|
||||||
* Any duplicates will appear as text only.
|
|
||||||
*
|
|
||||||
* @param locale
|
|
||||||
* the locale to apply; `null` value means no localization.
|
|
||||||
* @param format the format string (see [java.util.Formatter.format])
|
|
||||||
* @param args
|
|
||||||
* the list of arguments passed to the formatter.
|
|
||||||
* @return the formatted string (with spans).
|
|
||||||
* @see String.format
|
|
||||||
*/
|
|
||||||
fun format(locale: Locale, format: CharSequence, vararg args: Any): SpannedString {
|
|
||||||
val out = SpannableStringBuilder(format)
|
|
||||||
|
|
||||||
var i = 0
|
|
||||||
var argAt = -1
|
|
||||||
|
|
||||||
while (i < out.length) {
|
|
||||||
val m = formatSequence.matcher(out)
|
|
||||||
if (!m.find(i)) break
|
|
||||||
i = m.start()
|
|
||||||
val exprEnd = m.end()
|
|
||||||
|
|
||||||
val argTerm = m.group(1)!!
|
|
||||||
val modTerm = m.group(2)
|
|
||||||
|
|
||||||
val cookedArg = when (val typeTerm = m.group(3)) {
|
|
||||||
"%" -> "%"
|
|
||||||
"n" -> "\n"
|
|
||||||
else -> {
|
|
||||||
val argItem = args[when (argTerm) {
|
|
||||||
"" -> ++argAt
|
|
||||||
"<" -> argAt
|
|
||||||
else -> Integer.parseInt(argTerm.substring(0, argTerm.length - 1)) - 1
|
|
||||||
}]
|
|
||||||
if (typeTerm == "s" && argItem is Spanned) argItem else {
|
|
||||||
String.format(locale, "%$modTerm$typeTerm", argItem)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
out.replace(i, exprEnd, cookedArg)
|
|
||||||
i += cookedArg.length
|
|
||||||
}
|
|
||||||
|
|
||||||
return SpannedString(out)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package be.mygod.vpnhotspot.util
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.net.wifi.SoftApConfiguration
|
||||||
|
import android.net.wifi.p2p.WifiP2pConfig
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
|
import java.lang.reflect.Method
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The central object for accessing all the useful blocked APIs. Thanks Google!
|
||||||
|
*
|
||||||
|
* Lazy cannot be used directly as it will create inner classes.
|
||||||
|
*/
|
||||||
|
@SuppressLint("BlockedPrivateApi", "DiscouragedPrivateApi")
|
||||||
|
@Suppress("FunctionName")
|
||||||
|
object UnblockCentral {
|
||||||
|
/**
|
||||||
|
* Retrieve this property before doing dangerous shit.
|
||||||
|
*/
|
||||||
|
private val init by lazy {
|
||||||
|
if (Build.VERSION.SDK_INT < 28) return@lazy
|
||||||
|
// TODO: fix this not working when targeting API 30+
|
||||||
|
val getDeclaredMethod = Class::class.java.getDeclaredMethod("getDeclaredMethod",
|
||||||
|
String::class.java, arrayOf<Class<*>>()::class.java)
|
||||||
|
val clazz = Class.forName("dalvik.system.VMRuntime")
|
||||||
|
val setHiddenApiExemptions = getDeclaredMethod(clazz, "setHiddenApiExemptions",
|
||||||
|
arrayOf(Array<String>::class.java)) as Method
|
||||||
|
setHiddenApiExemptions(clazz.getDeclaredMethod("getRuntime")(null), arrayOf(""))
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(31)
|
||||||
|
fun setUserConfiguration(clazz: Class<*>) = init.let {
|
||||||
|
clazz.getDeclaredMethod("setUserConfiguration", Boolean::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
@get:RequiresApi(31)
|
||||||
|
val SoftApConfiguration_BAND_TYPES get() = init.let {
|
||||||
|
SoftApConfiguration::class.java.getDeclaredField("BAND_TYPES").get(null) as IntArray
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(31)
|
||||||
|
fun getApInstanceIdentifier(clazz: Class<*>) = init.let { clazz.getDeclaredMethod("getApInstanceIdentifier") }
|
||||||
|
|
||||||
|
@get:RequiresApi(29)
|
||||||
|
val WifiP2pConfig_Builder_mNetworkName get() = init.let {
|
||||||
|
WifiP2pConfig.Builder::class.java.getDeclaredField("mNetworkName").apply { isAccessible = true }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,17 +4,13 @@ import android.annotation.SuppressLint
|
|||||||
import android.annotation.TargetApi
|
import android.annotation.TargetApi
|
||||||
import android.content.*
|
import android.content.*
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.net.InetAddresses
|
import android.net.*
|
||||||
import android.net.LinkProperties
|
|
||||||
import android.net.RouteInfo
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.RemoteException
|
import android.os.RemoteException
|
||||||
import android.system.ErrnoException
|
import android.system.ErrnoException
|
||||||
import android.system.Os
|
import android.system.Os
|
||||||
import android.system.OsConstants
|
import android.system.OsConstants
|
||||||
import android.text.Spannable
|
import android.text.*
|
||||||
import android.text.SpannableString
|
|
||||||
import android.text.SpannableStringBuilder
|
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
@@ -39,6 +35,7 @@ import java.lang.reflect.Method
|
|||||||
import java.net.InetAddress
|
import java.net.InetAddress
|
||||||
import java.net.NetworkInterface
|
import java.net.NetworkInterface
|
||||||
import java.net.SocketException
|
import java.net.SocketException
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
tailrec fun Throwable.getRootCause(): Throwable {
|
tailrec fun Throwable.getRootCause(): Throwable {
|
||||||
if (this is InvocationTargetException || this is RemoteException) return (cause ?: return this).getRootCause()
|
if (this is InvocationTargetException || this is RemoteException) return (cause ?: return this).getRootCause()
|
||||||
@@ -56,6 +53,19 @@ fun Long.toPluralInt(): Int {
|
|||||||
return (this % 1000000000).toInt() + 1000000000
|
return (this % 1000000000).toInt() + 1000000000
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RequiresApi(26)
|
||||||
|
fun Method.matches(name: String, vararg classes: Class<*>) = this.name == name && parameterCount == classes.size &&
|
||||||
|
classes.indices.all { i -> parameters[i].type == classes[i] }
|
||||||
|
@RequiresApi(26)
|
||||||
|
inline fun <reified T> Method.matches1(name: String) = matches(name, T::class.java)
|
||||||
|
|
||||||
|
fun Method.matchesCompat(name: String, args: Array<out Any?>?, vararg classes: Class<*>) =
|
||||||
|
if (Build.VERSION.SDK_INT < 26) {
|
||||||
|
this.name == name && args?.size ?: 0 == classes.size && classes.indices.all { i ->
|
||||||
|
args!![i]?.let { classes[i].isInstance(it) } != false
|
||||||
|
}
|
||||||
|
} else matches(name, *classes)
|
||||||
|
|
||||||
fun Context.ensureReceiverUnregistered(receiver: BroadcastReceiver) {
|
fun Context.ensureReceiverUnregistered(receiver: BroadcastReceiver) {
|
||||||
try {
|
try {
|
||||||
unregisterReceiver(receiver)
|
unregisterReceiver(receiver)
|
||||||
@@ -80,6 +90,61 @@ fun setVisibility(view: View, value: Boolean) {
|
|||||||
view.isVisible = value
|
view.isVisible = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val formatSequence = "%([0-9]+\\$|<?)([^a-zA-z%]*)([[a-zA-Z%]&&[^tT]]|[tT][a-zA-Z])".toPattern()
|
||||||
|
/**
|
||||||
|
* Version of [String.format] that works on [Spanned] strings to preserve rich text formatting.
|
||||||
|
* Both the `format` as well as any `%s args` can be Spanned and will have their formatting preserved.
|
||||||
|
* Due to the way [Spannable]s work, any argument's spans will can only be included **once** in the result.
|
||||||
|
* Any duplicates will appear as text only.
|
||||||
|
*
|
||||||
|
* See also: https://github.com/george-steel/android-utils/blob/289aff11e53593a55d780f9f5986e49343a79e55/src/org/oshkimaadziig/george/androidutils/SpanFormatter.java
|
||||||
|
*
|
||||||
|
* @param locale
|
||||||
|
* the locale to apply; `null` value means no localization.
|
||||||
|
* @param args
|
||||||
|
* the list of arguments passed to the formatter.
|
||||||
|
* @return the formatted string (with spans).
|
||||||
|
* @see String.format
|
||||||
|
* @author George T. Steel
|
||||||
|
*/
|
||||||
|
fun CharSequence.format(locale: Locale, vararg args: Any) = SpannableStringBuilder(this).apply {
|
||||||
|
var i = 0
|
||||||
|
var argAt = -1
|
||||||
|
while (i < length) {
|
||||||
|
val m = formatSequence.matcher(this)
|
||||||
|
if (!m.find(i)) break
|
||||||
|
i = m.start()
|
||||||
|
val exprEnd = m.end()
|
||||||
|
val argTerm = m.group(1)!!
|
||||||
|
val modTerm = m.group(2)
|
||||||
|
val cookedArg = when (val typeTerm = m.group(3)) {
|
||||||
|
"%" -> "%"
|
||||||
|
"n" -> "\n"
|
||||||
|
else -> {
|
||||||
|
val argItem = args[when (argTerm) {
|
||||||
|
"" -> ++argAt
|
||||||
|
"<" -> argAt
|
||||||
|
else -> Integer.parseInt(argTerm.substring(0, argTerm.length - 1)) - 1
|
||||||
|
}]
|
||||||
|
if (typeTerm == "s" && argItem is Spanned) argItem else {
|
||||||
|
String.format(locale, "%$modTerm$typeTerm", argItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
replace(i, exprEnd, cookedArg)
|
||||||
|
i += cookedArg.length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> Iterable<T>.joinToSpanned(separator: CharSequence = ", ", prefix: CharSequence = "", postfix: CharSequence = "",
|
||||||
|
limit: Int = -1, truncated: CharSequence = "...",
|
||||||
|
transform: ((T) -> CharSequence)? = null) =
|
||||||
|
joinTo(SpannableStringBuilder(), separator, prefix, postfix, limit, truncated, transform)
|
||||||
|
fun <T> Sequence<T>.joinToSpanned(separator: CharSequence = ", ", prefix: CharSequence = "", postfix: CharSequence = "",
|
||||||
|
limit: Int = -1, truncated: CharSequence = "...",
|
||||||
|
transform: ((T) -> CharSequence)? = null) =
|
||||||
|
joinTo(SpannableStringBuilder(), separator, prefix, postfix, limit, truncated, transform)
|
||||||
|
|
||||||
fun makeIpSpan(ip: InetAddress) = ip.hostAddress.let {
|
fun makeIpSpan(ip: InetAddress) = ip.hostAddress.let {
|
||||||
// exclude all bogon IP addresses supported by Android APIs
|
// exclude all bogon IP addresses supported by Android APIs
|
||||||
if (!app.hasTouch || ip.isMulticastAddress || ip.isAnyLocalAddress || ip.isLoopbackAddress ||
|
if (!app.hasTouch || ip.isMulticastAddress || ip.isAnyLocalAddress || ip.isLoopbackAddress ||
|
||||||
@@ -184,6 +249,14 @@ 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")
|
@Suppress("FunctionName")
|
||||||
fun if_nametoindex(ifname: String) = if (Build.VERSION.SDK_INT >= 26) {
|
fun if_nametoindex(ifname: String) = if (Build.VERSION.SDK_INT >= 26) {
|
||||||
Os.if_nametoindex(ifname)
|
Os.if_nametoindex(ifname)
|
||||||
|
|||||||
@@ -37,6 +37,8 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:descendantFocusability="beforeDescendants"
|
android:descendantFocusability="beforeDescendants"
|
||||||
android:focusableInTouchMode="true"
|
android:focusableInTouchMode="true"
|
||||||
|
android:layout_marginStart="0dp"
|
||||||
|
android:layout_marginEnd="0dp"
|
||||||
style="@style/wifi_item">
|
style="@style/wifi_item">
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/ssid_wrapper"
|
android:id="@+id/ssid_wrapper"
|
||||||
@@ -119,103 +121,236 @@
|
|||||||
android:inputType="number"
|
android:inputType="number"
|
||||||
android:maxLength="19" />
|
android:maxLength="19" />
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/band_wrapper"
|
android:id="@+id/band_group"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="8dip"
|
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
<com.google.android.material.divider.MaterialDivider
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="@style/wifi_item_divider" />
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
style="@style/wifi_item_label"
|
style="@style/wifi_item_subhead"
|
||||||
android:text="@string/wifi_hotspot_ap_band_title" />
|
android:text="@string/wifi_hotspot_ap_band_title" />
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dip"
|
||||||
|
style="@style/wifi_item_label"
|
||||||
|
android:text="@string/wifi_ap_choose_2G" />
|
||||||
<Spinner
|
<Spinner
|
||||||
android:id="@+id/band"
|
android:id="@+id/band_2G"
|
||||||
style="@style/wifi_item_content"
|
style="@style/wifi_item_content"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:minHeight="@dimen/touch_target_min"
|
android:minHeight="@dimen/touch_target_min"
|
||||||
android:prompt="@string/wifi_hotspot_ap_band_title" />
|
android:prompt="@string/wifi_ap_choose_2G" />
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dip"
|
||||||
|
style="@style/wifi_item_label"
|
||||||
|
android:text="@string/wifi_ap_choose_5G" />
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/band_5G"
|
||||||
|
style="@style/wifi_item_content"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:minHeight="@dimen/touch_target_min"
|
||||||
|
android:prompt="@string/wifi_ap_choose_5G" />
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/band_wrapper_6G"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dip"
|
||||||
|
android:orientation="vertical">
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="@style/wifi_item_label"
|
||||||
|
android:text="@string/wifi_ap_choose_6G" />
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/band_6G"
|
||||||
|
style="@style/wifi_item_content"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:minHeight="@dimen/touch_target_min"
|
||||||
|
android:prompt="@string/wifi_ap_choose_6G" />
|
||||||
|
</LinearLayout>
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/band_wrapper_60G"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dip"
|
||||||
|
android:orientation="vertical">
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="@style/wifi_item_label"
|
||||||
|
android:text="@string/wifi_ap_choose_60G" />
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/band_60G"
|
||||||
|
style="@style/wifi_item_content"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:minHeight="@dimen/touch_target_min"
|
||||||
|
android:prompt="@string/wifi_ap_choose_60G" />
|
||||||
|
</LinearLayout>
|
||||||
|
<Switch
|
||||||
|
android:id="@+id/bridged_mode"
|
||||||
|
style="@style/wifi_item_label"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dip"
|
||||||
|
android:minHeight="@dimen/touch_target_min"
|
||||||
|
android:text="@string/wifi_bridged_mode" />
|
||||||
|
<Switch
|
||||||
|
android:id="@+id/bridged_mode_opportunistic_shutdown"
|
||||||
|
style="@style/wifi_item_label"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dip"
|
||||||
|
android:minHeight="@dimen/touch_target_min"
|
||||||
|
android:text="@string/wifi_bridged_mode_opportunistic_shutdown" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
|
||||||
android:id="@+id/bssid_wrapper"
|
<LinearLayout
|
||||||
|
android:id="@+id/access_control_group"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="8dip"
|
android:orientation="vertical">
|
||||||
app:counterEnabled="true"
|
<com.google.android.material.divider.MaterialDivider
|
||||||
app:counterMaxLength="17"
|
|
||||||
app:errorEnabled="true"
|
|
||||||
android:hint="@string/wifi_advanced_mac_address_title">
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
|
||||||
android:id="@+id/bssid"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
style="@style/wifi_item_edit_content"
|
style="@style/wifi_item_divider" />
|
||||||
android:inputType="textNoSuggestions"
|
<TextView
|
||||||
android:maxLength="17" />
|
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
|
||||||
<Switch
|
|
||||||
android:id="@+id/hidden_ssid"
|
|
||||||
style="@style/wifi_item_label"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="8dip"
|
|
||||||
android:minHeight="@dimen/touch_target_min"
|
|
||||||
android:text="@string/wifi_hidden_network" />
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
|
||||||
android:id="@+id/max_client_wrapper"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="8dip"
|
|
||||||
app:counterEnabled="true"
|
|
||||||
app:counterMaxLength="10"
|
|
||||||
app:errorEnabled="true"
|
|
||||||
android:hint="@string/wifi_max_clients">
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
|
||||||
android:id="@+id/max_client"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
style="@style/wifi_item_edit_content"
|
style="@style/wifi_item_subhead"
|
||||||
android:inputType="number"
|
android:text="@string/wifi_hotspot_access_control_title" />
|
||||||
android:maxLength="10" />
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
android:id="@+id/max_client_wrapper"
|
||||||
<Switch
|
|
||||||
android:id="@+id/client_user_control"
|
|
||||||
style="@style/wifi_item_label"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="8dip"
|
|
||||||
android:minHeight="@dimen/touch_target_min"
|
|
||||||
android:text="@string/wifi_client_user_control" />
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
|
||||||
android:id="@+id/blocked_list_wrapper"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="8dip"
|
|
||||||
android:hint="@string/wifi_blocked_list"
|
|
||||||
app:errorEnabled="true">
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
|
||||||
android:id="@+id/blocked_list"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
style="@style/wifi_item_edit_content"
|
android:layout_marginTop="8dip"
|
||||||
android:inputType="textMultiLine|textNoSuggestions" />
|
app:counterEnabled="true"
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
app:counterMaxLength="10"
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
app:errorEnabled="true"
|
||||||
android:id="@+id/allowed_list_wrapper"
|
android:hint="@string/wifi_max_clients">
|
||||||
android:layout_width="match_parent"
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
android:layout_height="wrap_content"
|
android:id="@+id/max_client"
|
||||||
android:layout_marginTop="8dip"
|
android:layout_width="match_parent"
|
||||||
android:hint="@string/wifi_allowed_list"
|
android:layout_height="wrap_content"
|
||||||
app:errorEnabled="true">
|
style="@style/wifi_item_edit_content"
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
android:inputType="number"
|
||||||
android:id="@+id/allowed_list"
|
android:maxLength="10" />
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
<Switch
|
||||||
|
android:id="@+id/client_user_control"
|
||||||
|
style="@style/wifi_item_label"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
style="@style/wifi_item_edit_content"
|
android:layout_marginTop="8dip"
|
||||||
android:inputType="textMultiLine|textNoSuggestions" />
|
android:minHeight="@dimen/touch_target_min"
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
android:text="@string/wifi_client_user_control" />
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/blocked_list_wrapper"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dip"
|
||||||
|
android:hint="@string/wifi_blocked_list"
|
||||||
|
app:errorEnabled="true">
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/blocked_list"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="@style/wifi_item_edit_content"
|
||||||
|
android:inputType="textMultiLine|textNoSuggestions" />
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/allowed_list_wrapper"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dip"
|
||||||
|
android:hint="@string/wifi_allowed_list"
|
||||||
|
app:errorEnabled="true">
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/allowed_list"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="@style/wifi_item_edit_content"
|
||||||
|
android:inputType="textMultiLine|textNoSuggestions" />
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/advanced_ap_group"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
<com.google.android.material.divider.MaterialDivider
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="@style/wifi_item_divider" />
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="@style/wifi_item_subhead"
|
||||||
|
android:text="@string/wifi_hotspot_ap_advanced_title" />
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/bssid_wrapper"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dip"
|
||||||
|
app:counterEnabled="true"
|
||||||
|
app:counterMaxLength="17"
|
||||||
|
app:errorEnabled="true"
|
||||||
|
android:hint="@string/wifi_advanced_mac_address_title">
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/bssid"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="@style/wifi_item_edit_content"
|
||||||
|
android:inputType="textNoSuggestions"
|
||||||
|
android:maxLength="17" />
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
<Switch
|
||||||
|
android:id="@+id/mac_randomization"
|
||||||
|
style="@style/wifi_item_label"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dip"
|
||||||
|
android:minHeight="@dimen/touch_target_min"
|
||||||
|
android:text="@string/wifi_mac_randomization" />
|
||||||
|
<Switch
|
||||||
|
android:id="@+id/hidden_ssid"
|
||||||
|
style="@style/wifi_item_label"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dip"
|
||||||
|
android:minHeight="@dimen/touch_target_min"
|
||||||
|
android:text="@string/wifi_hidden_network" />
|
||||||
|
<Switch
|
||||||
|
android:id="@+id/ieee_80211ax"
|
||||||
|
style="@style/wifi_item_label"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dip"
|
||||||
|
android:minHeight="@dimen/touch_target_min"
|
||||||
|
android:text="@string/wifi_ieee_80211ax" />
|
||||||
|
<Switch
|
||||||
|
android:id="@+id/user_config"
|
||||||
|
style="@style/wifi_item_label"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dip"
|
||||||
|
android:minHeight="@dimen/touch_target_min"
|
||||||
|
android:text="@string/wifi_user_config" />
|
||||||
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|||||||
@@ -61,7 +61,9 @@
|
|||||||
<string name="wifi_hotspot_ap_band_title" msgid="1165801173359290681">"Диапазон частот Wi-Fi"</string>
|
<string name="wifi_hotspot_ap_band_title" msgid="1165801173359290681">"Диапазон частот Wi-Fi"</string>
|
||||||
<string name="wifi_ap_choose_auto" msgid="2677800651271769965">"Авто"</string>
|
<string name="wifi_ap_choose_auto" msgid="2677800651271769965">"Авто"</string>
|
||||||
<string name="wifi_ap_choose_2G" msgid="8724267386885036210">"2,4 ГГц"</string>
|
<string name="wifi_ap_choose_2G" msgid="8724267386885036210">"2,4 ГГц"</string>
|
||||||
<string name="wifi_ap_choose_5G" msgid="8813128641914385634">"5,0 ГГц"</string>
|
<string name="wifi_ap_choose_5G" msgid="8813128641914385634">"5 ГГц"</string>
|
||||||
|
<string name="wifi_ap_choose_6G">"6 ГГц"</string>
|
||||||
|
<string name="wifi_ap_choose_60G">"60 ГГц"</string>
|
||||||
<string name="wifi_advanced_mac_address_title" msgid="6571335466330978393">"MAC-адрес"</string>
|
<string name="wifi_advanced_mac_address_title" msgid="6571335466330978393">"MAC-адрес"</string>
|
||||||
<string name="wifi_hidden_network" msgid="973162091800925000">"Скрытая сеть"</string>
|
<string name="wifi_hidden_network" msgid="973162091800925000">"Скрытая сеть"</string>
|
||||||
<string name="wifi_save" msgid="3331121567988522826">"Сохранить"</string>
|
<string name="wifi_save" msgid="3331121567988522826">"Сохранить"</string>
|
||||||
|
|||||||
@@ -61,9 +61,21 @@
|
|||||||
<string name="tethering_manage_ncm">USB 网络共享 (NCM)</string>
|
<string name="tethering_manage_ncm">USB 网络共享 (NCM)</string>
|
||||||
<string name="tethering_manage_wigig">WiGig 热点</string>
|
<string name="tethering_manage_wigig">WiGig 热点</string>
|
||||||
<string name="tethering_manage_wifi_info">%1$d MHz, 频道 %2$d, 频宽 %3$s</string>
|
<string name="tethering_manage_wifi_info">%1$d MHz, 频道 %2$d, 频宽 %3$s</string>
|
||||||
|
<string name="tethering_manage_wifi_info_timeout_enabled">%4$s: Wi\u2011Fi %5$d, %1$d MHz, 频道 %2$d, 频宽 %3$s,
|
||||||
|
关闭延迟 %6$s</string>
|
||||||
|
<string name="tethering_manage_wifi_info_timeout_disabled">%4$s: Wi\u2011Fi %5$d, %1$d MHz, 频道 %2$d, 频宽 %3$s,
|
||||||
|
不自动关闭</string>
|
||||||
<plurals name="tethering_manage_wifi_capabilities">
|
<plurals name="tethering_manage_wifi_capabilities">
|
||||||
<item quantity="other">已连接 %1$s/%2$d 个设备\n支持功能:%3$s</item>
|
<item quantity="other">已连接 %1$s/%2$d 个设备\n支持功能:%3$s</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
|
<plurals name="tethering_manage_wifi_clients">
|
||||||
|
<item quantity="other">已连接 %d 个设备</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="tethering_manage_wifi_supported_channels">\n支持频道: %s</string>
|
||||||
|
<string name="tethering_manage_wifi_feature_ap_mac_randomization">随机接入点 MAC</string>
|
||||||
|
<string name="tethering_manage_wifi_feature_bridged_ap_concurrency">桥接 AP 并发</string>
|
||||||
|
<string name="tethering_manage_wifi_feature_sta_ap_concurrency">STA/AP 并发</string>
|
||||||
|
<string name="tethering_manage_wifi_feature_sta_bridged_ap_concurrency">STA/桥接 AP 并发</string>
|
||||||
<string name="tethering_manage_wifi_no_features">无</string>
|
<string name="tethering_manage_wifi_no_features">无</string>
|
||||||
<string name="tethering_manage_wifi_client_blocked">已屏蔽 %1$s:%2$s</string>
|
<string name="tethering_manage_wifi_client_blocked">已屏蔽 %1$s:%2$s</string>
|
||||||
<string name="tethering_manage_wifi_copy_mac">复制 MAC</string>
|
<string name="tethering_manage_wifi_copy_mac">复制 MAC</string>
|
||||||
@@ -140,6 +152,7 @@
|
|||||||
|
|
||||||
<string name="notification_tethering_title">VPN 共享已启用</string>
|
<string name="notification_tethering_title">VPN 共享已启用</string>
|
||||||
<string name="notification_channel_tethering">VPN 共享服务</string>
|
<string name="notification_channel_tethering">VPN 共享服务</string>
|
||||||
|
<string name="notification_channel_monitor">监视不活跃接口</string>
|
||||||
<plurals name="notification_connected_devices">
|
<plurals name="notification_connected_devices">
|
||||||
<item quantity="other">%d 个设备已连接到 %s</item>
|
<item quantity="other">%d 个设备已连接到 %s</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
@@ -166,16 +179,25 @@
|
|||||||
<string name="wifi_hotspot_timeout">关闭延迟</string>
|
<string name="wifi_hotspot_timeout">关闭延迟</string>
|
||||||
<string name="wifi_hotspot_timeout_default">默认延迟:%d 毫秒</string>
|
<string name="wifi_hotspot_timeout_default">默认延迟:%d 毫秒</string>
|
||||||
<string name="wifi_hotspot_ap_band_title" msgid="1165801173359290681">"AP 频段"</string>
|
<string name="wifi_hotspot_ap_band_title" msgid="1165801173359290681">"AP 频段"</string>
|
||||||
|
<string name="wifi_ap_choose_disabled">Disabled</string>
|
||||||
<string name="wifi_ap_choose_auto" msgid="2677800651271769965">"自动"</string>
|
<string name="wifi_ap_choose_auto" msgid="2677800651271769965">"自动"</string>
|
||||||
<string name="wifi_ap_choose_2G" msgid="8724267386885036210">"2.4 GHz 频段"</string>
|
<string name="wifi_ap_choose_2G" msgid="8724267386885036210">"2.4 GHz 频段"</string>
|
||||||
<string name="wifi_ap_choose_5G" msgid="8813128641914385634">"5.0 GHz 频段"</string>
|
<string name="wifi_ap_choose_5G" msgid="8813128641914385634">"5 GHz 频段"</string>
|
||||||
<string name="wifi_ap_choose_6G">6.0 GHz 频段</string>
|
<string name="wifi_ap_choose_6G">6 GHz 频段</string>
|
||||||
|
<string name="wifi_ap_choose_60G">60 GHz 频段</string>
|
||||||
|
<string name="wifi_hotspot_access_control_title">访问控制</string>
|
||||||
|
<string name="wifi_hotspot_ap_advanced_title">高级接入点设置</string>
|
||||||
<string name="wifi_advanced_mac_address_title" msgid="6571335466330978393">"MAC 地址"</string>
|
<string name="wifi_advanced_mac_address_title" msgid="6571335466330978393">"MAC 地址"</string>
|
||||||
<string name="wifi_hidden_network" msgid="973162091800925000">"隐藏的网络"</string>
|
<string name="wifi_hidden_network" msgid="973162091800925000">"隐藏的网络"</string>
|
||||||
<string name="wifi_max_clients">允许连接设备数上限</string>
|
<string name="wifi_max_clients">允许连接设备数上限</string>
|
||||||
<string name="wifi_client_user_control">过滤可以连接的设备</string>
|
<string name="wifi_client_user_control">过滤可以连接的设备</string>
|
||||||
<string name="wifi_blocked_list">设备黑名单</string>
|
<string name="wifi_blocked_list">设备黑名单</string>
|
||||||
<string name="wifi_allowed_list">设备白名单</string>
|
<string name="wifi_allowed_list">设备白名单</string>
|
||||||
|
<string name="wifi_mac_randomization">随机生成 MAC 地址</string>
|
||||||
|
<string name="wifi_bridged_mode">启用无线接入点桥接模式</string>
|
||||||
|
<string name="wifi_bridged_mode_opportunistic_shutdown">启用桥接模式伺机关闭</string>
|
||||||
|
<string name="wifi_ieee_80211ax">启用 Wi\u2011Fi 6</string>
|
||||||
|
<string name="wifi_user_config">用户提供配置</string>
|
||||||
<string name="wifi_save" msgid="3331121567988522826">"保存"</string>
|
<string name="wifi_save" msgid="3331121567988522826">"保存"</string>
|
||||||
|
|
||||||
<!-- Based on: https://github.com/PrivacyApps/donations/blob/747d36a18433c7e9329691054122a8ad337a62d2/Donations/src/main/res/values-zh/donations__strings.xml -->
|
<!-- Based on: https://github.com/PrivacyApps/donations/blob/747d36a18433c7e9329691054122a8ad337a62d2/Donations/src/main/res/values-zh/donations__strings.xml -->
|
||||||
|
|||||||
@@ -71,6 +71,9 @@
|
|||||||
<plurals name="tethering_manage_wifi_capabilities">
|
<plurals name="tethering_manage_wifi_capabilities">
|
||||||
<item quantity="other">已連接 %1$s/%2$d 個設備\n支持功能:%3$s</item>
|
<item quantity="other">已連接 %1$s/%2$d 個設備\n支持功能:%3$s</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
|
<plurals name="tethering_manage_wifi_clients">
|
||||||
|
<item quantity="other">已連接 %d 個設備</item>
|
||||||
|
</plurals>
|
||||||
<string name="tethering_manage_wifi_no_features">無</string>
|
<string name="tethering_manage_wifi_no_features">無</string>
|
||||||
<string name="tethering_manage_wifi_client_blocked">已隱藏 %1$s:%2$s</string>
|
<string name="tethering_manage_wifi_client_blocked">已隱藏 %1$s:%2$s</string>
|
||||||
<string name="tethering_manage_wifi_copy_mac">複製 MAC</string>
|
<string name="tethering_manage_wifi_copy_mac">複製 MAC</string>
|
||||||
@@ -173,16 +176,20 @@
|
|||||||
<string name="wifi_hotspot_timeout">關閉延遲時間</string>
|
<string name="wifi_hotspot_timeout">關閉延遲時間</string>
|
||||||
<string name="wifi_hotspot_timeout_default">默認延遲:%d 毫秒</string>
|
<string name="wifi_hotspot_timeout_default">默認延遲:%d 毫秒</string>
|
||||||
<string name="wifi_hotspot_ap_band_title" msgid="1165801173359290681">AP 頻帶</string>
|
<string name="wifi_hotspot_ap_band_title" msgid="1165801173359290681">AP 頻帶</string>
|
||||||
|
<string name="wifi_ap_choose_disabled">停用</string>
|
||||||
<string name="wifi_ap_choose_auto" msgid="2677800651271769965">自動</string>
|
<string name="wifi_ap_choose_auto" msgid="2677800651271769965">自動</string>
|
||||||
<string name="wifi_ap_choose_2G" msgid="8724267386885036210">2.4 GHz 頻帶</string>
|
<string name="wifi_ap_choose_2G" msgid="8724267386885036210">2.4 GHz 頻帶</string>
|
||||||
<string name="wifi_ap_choose_5G" msgid="8813128641914385634">5.0 GHz 頻帶</string>
|
<string name="wifi_ap_choose_5G" msgid="8813128641914385634">5 GHz 頻帶</string>
|
||||||
<string name="wifi_ap_choose_6G">6.0 GHz 頻帶</string>
|
<string name="wifi_ap_choose_6G">6 GHz 頻帶</string>
|
||||||
|
<string name="wifi_ap_choose_60G">60 GHz 頻帶</string>
|
||||||
<string name="wifi_advanced_mac_address_title" msgid="6571335466330978393">"MAC 地址"</string>
|
<string name="wifi_advanced_mac_address_title" msgid="6571335466330978393">"MAC 地址"</string>
|
||||||
<string name="wifi_hidden_network" msgid="973162091800925000">"隱藏的網路"</string>
|
<string name="wifi_hidden_network" msgid="973162091800925000">"隱藏的網路"</string>
|
||||||
<string name="wifi_max_clients">允許的連接裝置數量</string>
|
<string name="wifi_max_clients">允許的連接裝置數量</string>
|
||||||
<string name="wifi_client_user_control">過濾可以連接的裝置</string>
|
<string name="wifi_client_user_control">過濾可以連接的裝置</string>
|
||||||
<string name="wifi_blocked_list">裝置黑名單</string>
|
<string name="wifi_blocked_list">裝置黑名單</string>
|
||||||
<string name="wifi_allowed_list">裝置白名單</string>
|
<string name="wifi_allowed_list">裝置白名單</string>
|
||||||
|
<string name="wifi_mac_randomization">隨機化 MAC 位址</string>
|
||||||
|
<string name="wifi_ieee_80211ax">啟用 Wi\u2011Fi 6</string>
|
||||||
<string name="wifi_save" msgid="3331121567988522826">儲存</string>
|
<string name="wifi_save" msgid="3331121567988522826">儲存</string>
|
||||||
|
|
||||||
<!-- Based on: https://github.com/PrivacyApps/donations/blob/747d36a18433c7e9329691054122a8ad337a62d2/Donations/src/main/res/values-zh/donations__strings.xml -->
|
<!-- Based on: https://github.com/PrivacyApps/donations/blob/747d36a18433c7e9329691054122a8ad337a62d2/Donations/src/main/res/values-zh/donations__strings.xml -->
|
||||||
|
|||||||
@@ -73,10 +73,23 @@
|
|||||||
<string name="tethering_manage_ncm">USB tethering (NCM)</string>
|
<string name="tethering_manage_ncm">USB tethering (NCM)</string>
|
||||||
<string name="tethering_manage_wigig">WiGig hotspot</string>
|
<string name="tethering_manage_wigig">WiGig hotspot</string>
|
||||||
<string name="tethering_manage_wifi_info">%1$d MHz, channel %2$d, width %3$s</string>
|
<string name="tethering_manage_wifi_info">%1$d MHz, channel %2$d, width %3$s</string>
|
||||||
|
<string name="tethering_manage_wifi_info_timeout_enabled">%4$s: Wi\u2011Fi %5$d, %1$d MHz, channel %2$d,
|
||||||
|
width %3$s, idle timeout in %6$s</string>
|
||||||
|
<string name="tethering_manage_wifi_info_timeout_disabled">%4$s: Wi\u2011Fi %5$d, %1$d MHz, channel %2$d,
|
||||||
|
width %3$s, idle timeout disabled</string>
|
||||||
<plurals name="tethering_manage_wifi_capabilities">
|
<plurals name="tethering_manage_wifi_capabilities">
|
||||||
<item quantity="one">%1$s/%2$d client connected\nSupported features: %3$s</item>
|
<item quantity="one">%1$s/%2$d client connected\nSupported features: %3$s</item>
|
||||||
<item quantity="other">%1$s/%2$d clients connected\nSupported features: %3$s</item>
|
<item quantity="other">%1$s/%2$d clients connected\nSupported features: %3$s</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
|
<plurals name="tethering_manage_wifi_clients">
|
||||||
|
<item quantity="one">%d client connected</item>
|
||||||
|
<item quantity="other">%1d clients connected</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="tethering_manage_wifi_supported_channels">\nSupported channels: %s</string>
|
||||||
|
<string name="tethering_manage_wifi_feature_ap_mac_randomization">Randomized AP MAC</string>
|
||||||
|
<string name="tethering_manage_wifi_feature_bridged_ap_concurrency">Bridged AP concurrency</string>
|
||||||
|
<string name="tethering_manage_wifi_feature_sta_ap_concurrency">STA + AP concurrency</string>
|
||||||
|
<string name="tethering_manage_wifi_feature_sta_bridged_ap_concurrency">STA + Bridged AP concurrency</string>
|
||||||
<string name="tethering_manage_wifi_no_features">None</string>
|
<string name="tethering_manage_wifi_no_features">None</string>
|
||||||
<string name="tethering_manage_wifi_client_blocked">Blocked %1$s: %2$s</string>
|
<string name="tethering_manage_wifi_client_blocked">Blocked %1$s: %2$s</string>
|
||||||
<string name="tethering_manage_wifi_copy_mac">Copy MAC</string>
|
<string name="tethering_manage_wifi_copy_mac">Copy MAC</string>
|
||||||
@@ -157,8 +170,9 @@
|
|||||||
<string name="settings_restart_required">Restart this app to apply this setting.</string>
|
<string name="settings_restart_required">Restart this app to apply this setting.</string>
|
||||||
<string name="settings_exit_app">Exit</string>
|
<string name="settings_exit_app">Exit</string>
|
||||||
|
|
||||||
<string name="notification_tethering_title">VPN tethering active</string>
|
<string name="notification_tethering_title">VPN tethering</string>
|
||||||
<string name="notification_channel_tethering">VPN Tethering Service</string>
|
<string name="notification_channel_tethering">VPN Tethering Service</string>
|
||||||
|
<string name="notification_channel_monitor">Monitor Inactive Interfaces</string>
|
||||||
<plurals name="notification_connected_devices">
|
<plurals name="notification_connected_devices">
|
||||||
<item quantity="one">%d device connected to %s</item>
|
<item quantity="one">%d device connected to %s</item>
|
||||||
<item quantity="other">%d devices connected to %s</item>
|
<item quantity="other">%d devices connected to %s</item>
|
||||||
@@ -188,16 +202,25 @@
|
|||||||
<string name="wifi_hotspot_timeout">Inactive timeout</string>
|
<string name="wifi_hotspot_timeout">Inactive timeout</string>
|
||||||
<string name="wifi_hotspot_timeout_default">Default timeout: %dms</string>
|
<string name="wifi_hotspot_timeout_default">Default timeout: %dms</string>
|
||||||
<string name="wifi_hotspot_ap_band_title">AP Band</string>
|
<string name="wifi_hotspot_ap_band_title">AP Band</string>
|
||||||
|
<string name="wifi_ap_choose_disabled">Disabled</string>
|
||||||
<string name="wifi_ap_choose_auto">Auto</string>
|
<string name="wifi_ap_choose_auto">Auto</string>
|
||||||
<string name="wifi_ap_choose_2G">2.4 GHz Band</string>
|
<string name="wifi_ap_choose_2G">2.4 GHz Band</string>
|
||||||
<string name="wifi_ap_choose_5G">5.0 GHz Band</string>
|
<string name="wifi_ap_choose_5G">5 GHz Band</string>
|
||||||
<string name="wifi_ap_choose_6G">6.0 GHz Band</string>
|
<string name="wifi_ap_choose_6G">6 GHz Band</string>
|
||||||
|
<string name="wifi_ap_choose_60G">60 GHz Band</string>
|
||||||
|
<string name="wifi_hotspot_access_control_title">Access Control</string>
|
||||||
|
<string name="wifi_hotspot_ap_advanced_title">Advanced AP Options</string>
|
||||||
<string name="wifi_advanced_mac_address_title">MAC address</string>
|
<string name="wifi_advanced_mac_address_title">MAC address</string>
|
||||||
<string name="wifi_hidden_network">Hidden network</string>
|
<string name="wifi_hidden_network">Hidden network</string>
|
||||||
<string name="wifi_max_clients">Maximum number of clients</string>
|
<string name="wifi_max_clients">Maximum number of clients</string>
|
||||||
<string name="wifi_client_user_control">Control which client can use hotspot</string>
|
<string name="wifi_client_user_control">Control which client can use hotspot</string>
|
||||||
<string name="wifi_blocked_list">Blocked list of clients</string>
|
<string name="wifi_blocked_list">Blocked list of clients</string>
|
||||||
<string name="wifi_allowed_list">Allowed list of clients</string>
|
<string name="wifi_allowed_list">Allowed list of clients</string>
|
||||||
|
<string name="wifi_mac_randomization">Use randomized MAC</string>
|
||||||
|
<string name="wifi_bridged_mode">Enable Bridged Access point (AP) concurrency</string>
|
||||||
|
<string name="wifi_bridged_mode_opportunistic_shutdown">Enable Bridged mode opportunistic shutdown</string>
|
||||||
|
<string name="wifi_ieee_80211ax">Enable Wi\u2011Fi 6</string>
|
||||||
|
<string name="wifi_user_config">User Supplied Configuration</string>
|
||||||
<string name="wifi_save">Save</string>
|
<string name="wifi_save">Save</string>
|
||||||
|
|
||||||
<!-- Based on: https://github.com/PrivacyApps/donations/blob/747d36a18433c7e9329691054122a8ad337a62d2/Donations/src/main/res/values/donations__strings.xml -->
|
<!-- Based on: https://github.com/PrivacyApps/donations/blob/747d36a18433c7e9329691054122a8ad337a62d2/Donations/src/main/res/values/donations__strings.xml -->
|
||||||
|
|||||||
@@ -43,5 +43,16 @@
|
|||||||
<item name="android:layout_marginStart">4dip</item>
|
<item name="android:layout_marginStart">4dip</item>
|
||||||
<item name="android:textSize">18sp</item>
|
<item name="android:textSize">18sp</item>
|
||||||
</style>
|
</style>
|
||||||
|
<style name="wifi_item_divider">
|
||||||
|
<item name="dividerInsetStart">8dip</item>
|
||||||
|
<item name="android:layout_marginTop">8dip</item>
|
||||||
|
</style>
|
||||||
|
<style name="wifi_item_subhead">
|
||||||
|
<item name="android:layout_marginTop">8dip</item>
|
||||||
|
<item name="android:paddingStart">8dip</item>
|
||||||
|
<item name="android:textAppearance">@style/TextAppearance.MaterialComponents.Subtitle1</item>
|
||||||
|
<item name="android:textColor">?attr/colorPrimary</item>
|
||||||
|
<item name="android:textStyle">bold</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
9
mobile/src/main/res/xml/no_backup.xml
Normal file
9
mobile/src/main/res/xml/no_backup.xml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<data-extraction-rules>
|
||||||
|
<cloud-backup>
|
||||||
|
<include domain="file" path="nonexistent" />
|
||||||
|
</cloud-backup>
|
||||||
|
<device-transfer>
|
||||||
|
<include domain="file" path="nonexistent" />
|
||||||
|
</device-transfer>
|
||||||
|
</data-extraction-rules>
|
||||||
Reference in New Issue
Block a user