keys = headers.keySet();
27 | for (String headerKey : keys) {
28 | builder.addHeader(headerKey, headers.get(headerKey)).build();
29 | }
30 | }
31 | //请求信息
32 | return chain.proceed(builder.build());
33 | }
34 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/http/interceptor/CacheInterceptor.java:
--------------------------------------------------------------------------------
1 | package com.jtun.router.http.interceptor;
2 |
3 | import android.content.Context;
4 |
5 | import com.jtun.router.http.NetworkUtil;
6 |
7 | import java.io.IOException;
8 |
9 | import okhttp3.CacheControl;
10 | import okhttp3.Interceptor;
11 | import okhttp3.Request;
12 | import okhttp3.Response;
13 |
14 | /**
15 | * Created by goldze on 2017/5/10.
16 | * 无网络状态下智能读取缓存的拦截器
17 | */
18 | public class CacheInterceptor implements Interceptor {
19 |
20 | private Context context;
21 |
22 | public CacheInterceptor(Context context) {
23 | this.context = context;
24 | }
25 |
26 | @Override
27 | public Response intercept(Chain chain) throws IOException {
28 | Request request = chain.request();
29 | if (NetworkUtil.isNetworkAvailable(context)) {
30 | Response response = chain.proceed(request);
31 | // read from cache for 60 s
32 | int maxAge = 60;
33 | return response.newBuilder()
34 | .removeHeader("Pragma")
35 | .removeHeader("Cache-Control")
36 | .header("Cache-Control", "public, max-age=" + maxAge)
37 | .build();
38 | } else {
39 | //读取缓存信息
40 | request = request.newBuilder()
41 | .cacheControl(CacheControl.FORCE_CACHE)
42 | .build();
43 | Response response = chain.proceed(request);
44 | //set cache times is 3 days
45 | int maxStale = 60 * 60 * 24 * 3;
46 | return response.newBuilder()
47 | .removeHeader("Pragma")
48 | .removeHeader("Cache-Control")
49 | .header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
50 | .build();
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/http/interceptor/ProgressInterceptor.java:
--------------------------------------------------------------------------------
1 | package com.jtun.router.http.interceptor;
2 |
3 |
4 | import com.jtun.router.http.download.ProgressResponseBody;
5 |
6 | import java.io.IOException;
7 |
8 | import okhttp3.Interceptor;
9 | import okhttp3.Response;
10 |
11 | /**
12 | * Created by goldze on 2017/5/10.
13 | */
14 |
15 | public class ProgressInterceptor implements Interceptor {
16 |
17 | @Override
18 | public Response intercept(Chain chain) throws IOException {
19 | String url = chain.request().url().url().toString();
20 | Response originalResponse = chain.proceed(chain.request());
21 | return originalResponse.newBuilder()
22 | .body(new ProgressResponseBody(originalResponse.body(),url))
23 | .build();
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/http/interceptor/logging/I.java:
--------------------------------------------------------------------------------
1 | package com.jtun.router.http.interceptor.logging;
2 |
3 |
4 | import java.util.logging.Level;
5 |
6 | import okhttp3.internal.platform.Platform;
7 |
8 | /**
9 | * @author ihsan on 10/02/2017.
10 | */
11 | class I {
12 |
13 | protected I() {
14 | throw new UnsupportedOperationException();
15 | }
16 |
17 | static void log(int type, String tag, String msg) {
18 | java.util.logging.Logger logger = java.util.logging.Logger.getLogger(tag);
19 | switch (type) {
20 | case Platform.INFO:
21 | logger.log(Level.INFO, msg);
22 | break;
23 | default:
24 | logger.log(Level.WARNING, msg);
25 | break;
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/http/interceptor/logging/Level.java:
--------------------------------------------------------------------------------
1 | package com.jtun.router.http.interceptor.logging;
2 |
3 | /**
4 | * @author ihsan on 21/02/2017.
5 | */
6 |
7 | public enum Level {
8 | /**
9 | * No logs.
10 | */
11 | NONE,
12 | /**
13 | * Example:
14 | *
{@code
15 | * - URL
16 | * - Method
17 | * - Headers
18 | * - Body
19 | * }
20 | */
21 | BASIC,
22 | /**
23 | * Example:
24 | *
{@code
25 | * - URL
26 | * - Method
27 | * - Headers
28 | * }
29 | */
30 | HEADERS,
31 | /**
32 | * Example:
33 | *
{@code
34 | * - URL
35 | * - Method
36 | * - Body
37 | * }
38 | */
39 | BODY
40 | }
41 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/http/interceptor/logging/Logger.java:
--------------------------------------------------------------------------------
1 | package com.jtun.router.http.interceptor.logging;
2 |
3 | import okhttp3.internal.platform.Platform;
4 |
5 | /**
6 | * @author ihsan on 11/07/2017.
7 | */
8 | @SuppressWarnings({"WeakerAccess", "unused"})
9 | public interface Logger {
10 | void log(int level, String tag, String msg);
11 |
12 | Logger DEFAULT = new Logger() {
13 | @Override
14 | public void log(int level, String tag, String message) {
15 | Platform.get().log(level, message, null);
16 | }
17 | };
18 | }
19 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/http/request/BaseRequest.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.http.request
2 |
3 | data class BaseRequest (val cmd:Int)
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/http/response/BaseResponse.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.http.response
2 |
3 | import com.google.gson.Gson
4 |
5 | data class BaseResponse(val code :Int = 0,
6 | val message:String = "success",
7 | val data : Any? = Any()){
8 |
9 | fun toJsonString():String{
10 | return Gson().toJson(this)
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/http/response/InternetInfo.java:
--------------------------------------------------------------------------------
1 | package com.jtun.router.http.response;
2 |
3 | public class InternetInfo {
4 | private String iccid = "iccid";
5 | private String imei = "imei";
6 | private String operatorName;
7 | private String networkType;
8 | private int BAND;
9 | private int earfcn;
10 | private int RSRP;
11 | private int RSSI;
12 | private int RERQ;
13 | private int CellID;
14 | private int PCI;
15 |
16 | public String getIccid() {
17 | return iccid;
18 | }
19 |
20 | public void setIccid(String iccid) {
21 | this.iccid = iccid;
22 | }
23 |
24 | public String getImei() {
25 | return imei;
26 | }
27 |
28 | public void setImei(String imei) {
29 | this.imei = imei;
30 | }
31 |
32 | public String getOperatorName() {
33 | return operatorName;
34 | }
35 |
36 | public void setOperatorName(String operatorName) {
37 | this.operatorName = operatorName;
38 | }
39 |
40 | public String getNetworkType() {
41 | return networkType;
42 | }
43 |
44 | public void setNetworkType(String networkType) {
45 | this.networkType = networkType;
46 | }
47 |
48 | public int getBAND() {
49 | return BAND;
50 | }
51 |
52 | public void setBAND(int BAND) {
53 | this.BAND = BAND;
54 | }
55 |
56 | public int getEarfcn() {
57 | return earfcn;
58 | }
59 |
60 | public void setEarfcn(int earfcn) {
61 | this.earfcn = earfcn;
62 | }
63 |
64 | public int getRSRP() {
65 | return RSRP;
66 | }
67 |
68 | public void setRSRP(int RSRP) {
69 | this.RSRP = RSRP;
70 | }
71 |
72 | public int getRSSI() {
73 | return RSSI;
74 | }
75 |
76 | public void setRSSI(int RSSI) {
77 | this.RSSI = RSSI;
78 | }
79 |
80 | public int getRERQ() {
81 | return RERQ;
82 | }
83 |
84 | public void setRERQ(int RERQ) {
85 | this.RERQ = RERQ;
86 | }
87 |
88 | public int getCellID() {
89 | return CellID;
90 | }
91 |
92 | public void setCellID(int cellID) {
93 | CellID = cellID;
94 | }
95 |
96 | public int getPCI() {
97 | return PCI;
98 | }
99 |
100 | public void setPCI(int PCI) {
101 | this.PCI = PCI;
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/http/response/NetSpeed.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.http.response
2 |
3 | data class NetSpeed(val totalRxBytes:Long,val totalTxBytes:Long,val state:Int,val numClients:Int)
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/http/response/SocketDeviceInfo.java:
--------------------------------------------------------------------------------
1 | package com.jtun.router.http.response;
2 |
3 | import com.jtun.router.http.ApConfig;
4 | import com.jtun.router.room.ClientConnected;
5 |
6 | import java.util.List;
7 |
8 | public class SocketDeviceInfo {
9 | private ApConfig apconfig;
10 | private DeviceInfo device;
11 | private List client;
12 |
13 | public ApConfig getApconfig() {
14 | return apconfig;
15 | }
16 |
17 | public void setApconfig(ApConfig apconfig) {
18 | this.apconfig = apconfig;
19 | }
20 |
21 | public DeviceInfo getDevice() {
22 | return device;
23 | }
24 |
25 | public void setDevice(DeviceInfo device) {
26 | this.device = device;
27 | }
28 |
29 | public List getClient() {
30 | return client;
31 | }
32 |
33 | public void setClient(List client) {
34 | this.client = client;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/http/response/UsedDataInfo.java:
--------------------------------------------------------------------------------
1 | package com.jtun.router.http.response;
2 |
3 | import com.jtun.router.control.WifiApControl;
4 |
5 | public class UsedDataInfo {
6 | private long today;
7 | private long month;
8 | private long all;
9 |
10 | public long getToday() {
11 | return today;
12 | }
13 |
14 | public void setToday(long today) {
15 | this.today = today;
16 | }
17 |
18 | public long getMonth() {
19 | return month;
20 | }
21 |
22 | public void setMonth(long month) {
23 | this.month = month;
24 | }
25 |
26 | public long getAll() {
27 | return all;
28 | }
29 |
30 | public void setAll(long all) {
31 | this.all = all;
32 | }
33 |
34 | public void initData(){
35 | today = WifiApControl.Companion.getInstance().getTodayUsed();
36 | month = WifiApControl.Companion.getInstance().getMonthUsed();
37 | all = WifiApControl.Companion.getInstance().getAllUsed();
38 | }
39 |
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/http/response/frp/FrpConfig.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.http.response.frp
2 |
3 | data class FrpConfig(val uid:String,val name:String,val cfg:String,val connecting:Boolean)
4 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/http/response/openvpn/ItemServer.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2024 Arne Schwabe
3 | * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
4 | */
5 |
6 | package com.jtun.router.http.response.openvpn
7 |
8 | data class ItemServer(val serverName:String,val port:String,val useUdp:Boolean,val enabled:Boolean,val timeout: Int)
9 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/http/response/openvpn/OpenvpnItem.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2024 Arne Schwabe
3 | * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
4 | */
5 |
6 | package com.jtun.router.http.response.openvpn
7 |
8 | /**
9 | * open vpn 配置信息
10 | * uuid 唯一标识
11 | * name 名称
12 | * message 连接显示状态
13 | * active 是否连接
14 | */
15 | data class OpenvpnItem (val uuid:String,val name:String,val message:String,val active:Boolean,val servers: List)
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/http/response/v2ray/Configs.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.http.response.v2ray
2 |
3 | data class Configs(val selected:String?,val isRunning:Boolean, val list: List)
4 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/http/response/v2ray/EConfigType.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.http.response.v2ray
2 |
3 | import com.jtun.router.config.Config
4 |
5 |
6 | enum class EConfigType(val value: Int, val protocolScheme: String) {
7 | VMESS(1, Config.VMESS),
8 | CUSTOM(2, Config.CUSTOM),
9 | SHADOWSOCKS(3, Config.SHADOWSOCKS),
10 | SOCKS(4, Config.SOCKS),
11 | VLESS(5, Config.VLESS),
12 | TROJAN(6, Config.TROJAN),
13 | WIREGUARD(7, Config.WIREGUARD);
14 |
15 | companion object {
16 | fun fromInt(value: Int) = values().firstOrNull { it.value == value }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/http/response/v2ray/ProfileItem.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.http.response.v2ray
2 |
3 | data class ProfileItem(
4 | val configType: EConfigType,
5 | var subscriptionId: String = "",
6 | var remarks: String = "",
7 | var server: String?,
8 | var serverPort: Int?,
9 | )
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/http/response/v2ray/ServersCache.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.http.response.v2ray
2 |
3 | data class ServersCache(
4 | val guid: String,
5 | val profile: ProfileItem
6 | )
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/net/DhcpWorkaround.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.net
2 |
3 | import android.content.SharedPreferences
4 | import com.jtun.router.App.Companion.app
5 | import com.jtun.router.net.Routing.Companion.IP
6 | import com.jtun.router.root.RoutingCommands
7 | import com.jtun.router.util.RootSession
8 | import com.jtun.router.widget.SmartSnackbar
9 | import kotlinx.coroutines.CancellationException
10 | import kotlinx.coroutines.GlobalScope
11 | import kotlinx.coroutines.launch
12 | import timber.log.Timber
13 | import java.io.IOException
14 |
15 | /**
16 | * Assuming RULE_PRIORITY_VPN_OUTPUT_TO_LOCAL = 11000.
17 | * Normally this is used to forward packets from remote to local, but it works anyways.
18 | * It just needs to be before RULE_PRIORITY_SECURE_VPN = 12000.
19 | * It would be great if we can gain better understanding into why this is only needed on some of the devices but not
20 | * others.
21 | *
22 | * Source: https://android.googlesource.com/platform/system/netd/+/b9baf26/server/RouteController.cpp#57
23 | */
24 | object DhcpWorkaround : SharedPreferences.OnSharedPreferenceChangeListener {
25 | private const val KEY_ENABLED = "service.dhcpWorkaround"
26 |
27 | init {
28 | app.pref.registerOnSharedPreferenceChangeListener(this)
29 | }
30 |
31 | val shouldEnable get() = app.pref.getBoolean(KEY_ENABLED, false)
32 | fun enable(enabled: Boolean) = GlobalScope.launch {
33 | val action = if (enabled) "add" else "del"
34 | try {
35 | RootSession.use {
36 | try {
37 | // ROUTE_TABLE_LOCAL_NETWORK: https://cs.android.com/android/platform/superproject/+/master:system/netd/server/RouteController.cpp;l=74;drc=b6dc40ac3d566d952d8445fc6ac796109c0cbc87
38 | it.exec("$IP rule $action iif lo uidrange 0-0 lookup 97 priority 11000")
39 | } catch (e: RoutingCommands.UnexpectedOutputException) {
40 | if (Routing.shouldSuppressIpError(e, enabled)) return@use
41 | Timber.w(IOException("Failed to tweak dhcp workaround rule", e))
42 | SmartSnackbar.make(e).show()
43 | }
44 | }
45 | } catch (_: CancellationException) {
46 | } catch (e: Exception) {
47 | Timber.w(e)
48 | SmartSnackbar.make(e).show()
49 | }
50 | }
51 |
52 | override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
53 | if (key == KEY_ENABLED) enable(shouldEnable)
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/net/InetAddressComparator.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.net
2 |
3 | import java.net.InetAddress
4 |
5 | object InetAddressComparator : Comparator {
6 | override fun compare(o1: InetAddress?, o2: InetAddress?): Int {
7 | if (o1 == null && o2 == null) return 0
8 | val a1 = o1?.address
9 | val a2 = o2?.address
10 | val r = (a1?.size ?: 0).compareTo(a2?.size ?: 0)
11 | return if (r == 0) a1!!.zip(a2!!).map { (l, r) -> l - r }.find { it != 0 } ?: 0 else r
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/net/MacAddressCompat.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.net
2 |
3 | import android.net.MacAddress
4 | import java.nio.ByteBuffer
5 | import java.nio.ByteOrder
6 |
7 | /**
8 | * This used to be a compat support class for [MacAddress].
9 | * Now it is just a convenient class for backwards compatibility.
10 | */
11 | @JvmInline
12 | value class MacAddressCompat(val addr: Long) {
13 | companion object {
14 | /**
15 | * The MacAddress zero MAC address.
16 | *
17 | * Not publicly exposed or treated specially since the OUI 00:00:00 is registered.
18 | */
19 | val ALL_ZEROS_ADDRESS = MacAddress.fromBytes(byteArrayOf(0, 0, 0, 0, 0, 0))
20 | val ANY_ADDRESS = MacAddress.fromBytes(byteArrayOf(2, 0, 0, 0, 0, 0))
21 |
22 | fun MacAddress.toLong() = ByteBuffer.allocate(Long.SIZE_BYTES).apply {
23 | order(ByteOrder.LITTLE_ENDIAN)
24 | put(toByteArray())
25 | rewind()
26 | }.long
27 | }
28 |
29 | fun toPlatform() = MacAddress.fromBytes(ByteBuffer.allocate(8).run {
30 | order(ByteOrder.LITTLE_ENDIAN)
31 | putLong(addr)
32 | array().take(6)
33 | }.toByteArray())
34 | }
35 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/net/TetherOffloadManager.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.net
2 |
3 | import android.provider.Settings
4 | import com.jtun.router.App.Companion.app
5 | import com.jtun.router.root.SettingsGlobalPut
6 |
7 | /**
8 | * It's hard to change tethering rules with Tethering hardware acceleration enabled for now.
9 | *
10 | * See also:
11 | * android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED
12 | * https://android.googlesource.com/platform/frameworks/base/+/android-8.1.0_r1/services/core/java/com/android/server/connectivity/tethering/OffloadHardwareInterface.java#45
13 | * https://android.googlesource.com/platform/hardware/qcom/data/ipacfg-mgr/+/master/msm8998/ipacm/src/IPACM_OffloadManager.cpp
14 | */
15 | object TetherOffloadManager {
16 | private const val TETHER_OFFLOAD_DISABLED = "tether_offload_disabled"
17 | val enabled get() = Settings.Global.getInt(app.contentResolver, TETHER_OFFLOAD_DISABLED, 0) == 0
18 | suspend fun setEnabled(value: Boolean) = SettingsGlobalPut.int(TETHER_OFFLOAD_DISABLED, if (value) 0 else 1)
19 | }
20 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/net/dns/VpnProtectedSelectorManager.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.net.dns
2 |
3 | import android.annotation.SuppressLint
4 | import android.net.VpnService
5 | import io.ktor.network.selector.SelectInterest
6 | import io.ktor.network.selector.Selectable
7 | import io.ktor.network.selector.SelectorManager
8 | import timber.log.Timber
9 | import java.net.ProtocolFamily
10 | import java.nio.channels.spi.SelectorProvider
11 |
12 | class VpnProtectedSelectorManager(private val manager: SelectorManager) : SelectorProvider(), SelectorManager {
13 | companion object {
14 | @SuppressLint("StaticFieldLeak")
15 | private val protector = VpnService()
16 | }
17 |
18 | private fun checkProtect(success: Boolean) {
19 | if (!success) Timber.w(Exception("protect failed"))
20 | }
21 |
22 | override fun openDatagramChannel() = manager.provider.openDatagramChannel().apply {
23 | checkProtect(protector.protect(socket()))
24 | }
25 | override fun openDatagramChannel(family: ProtocolFamily?) = manager.provider.openDatagramChannel(family).apply {
26 | checkProtect(protector.protect(socket()))
27 | }
28 | override fun openPipe() = manager.provider.openPipe()
29 | override fun openSelector() = manager.provider.openSelector()
30 | override fun openServerSocketChannel() = manager.provider.openServerSocketChannel()
31 | override fun openSocketChannel() = manager.provider.openSocketChannel().apply {
32 | checkProtect(protector.protect(socket()))
33 | }
34 |
35 | override val coroutineContext get() = manager.coroutineContext
36 | override val provider get() = this
37 | override fun close() = manager.close()
38 | override fun notifyClosed(selectable: Selectable) = manager.notifyClosed(selectable)
39 | override suspend fun select(selectable: Selectable, interest: SelectInterest) = manager.select(selectable, interest)
40 | }
41 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/net/monitor/DefaultNetworkMonitor.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.net.monitor
2 |
3 | import android.net.ConnectivityManager
4 | import android.net.LinkProperties
5 | import android.net.Network
6 | import android.net.NetworkCapabilities
7 | import android.os.Build
8 | import com.jtun.router.util.Services
9 | import com.jtun.router.util.globalNetworkRequestBuilder
10 | import kotlinx.coroutines.GlobalScope
11 | import kotlinx.coroutines.launch
12 |
13 | object DefaultNetworkMonitor : UpstreamMonitor() {
14 | private var registered = false
15 | override var currentLinkProperties: LinkProperties? = null
16 | private set
17 | /**
18 | * Unfortunately registerDefaultNetworkCallback is going to return VPN interface since Android P DP1:
19 | * https://android.googlesource.com/platform/frameworks/base/+/dda156ab0c5d66ad82bdcf76cda07cbc0a9c8a2e
20 | */
21 | private val networkRequest = globalNetworkRequestBuilder().apply {
22 | addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
23 | addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
24 | }.build()
25 | private val networkCallback = object : ConnectivityManager.NetworkCallback() {
26 | override fun onAvailable(network: Network) {
27 | val properties = Services.connectivity.getLinkProperties(network)
28 | val callbacks = synchronized(this@DefaultNetworkMonitor) {
29 | currentNetwork = network
30 | currentLinkProperties = properties
31 | callbacks.toList()
32 | }
33 | GlobalScope.launch { callbacks.forEach { it.onAvailable(properties) } }
34 | }
35 |
36 | override fun onLinkPropertiesChanged(network: Network, properties: LinkProperties) {
37 | val callbacks = synchronized(this@DefaultNetworkMonitor) {
38 | currentNetwork = network
39 | currentLinkProperties = properties
40 | callbacks.toList()
41 | }
42 | GlobalScope.launch { callbacks.forEach { it.onAvailable(properties) } }
43 | }
44 |
45 | override fun onLost(network: Network) {
46 | val callbacks = synchronized(this@DefaultNetworkMonitor) {
47 | currentNetwork = null
48 | currentLinkProperties = null
49 | callbacks.toList()
50 | }
51 | GlobalScope.launch { callbacks.forEach { it.onAvailable() } }
52 | }
53 | }
54 |
55 | override fun registerCallbackLocked(callback: Callback) {
56 | if (registered) {
57 | val currentLinkProperties = currentLinkProperties
58 | if (currentLinkProperties != null) GlobalScope.launch {
59 | callback.onAvailable(currentLinkProperties)
60 | }
61 | } else {
62 | if (Build.VERSION.SDK_INT >= 31) {
63 | Services.connectivity.registerBestMatchingNetworkCallback(networkRequest, networkCallback,
64 | Services.mainHandler)
65 | } else Services.connectivity.requestNetwork(networkRequest, networkCallback, Services.mainHandler)
66 | registered = true
67 | }
68 | }
69 |
70 | override fun destroyLocked() {
71 | if (!registered) return
72 | Services.connectivity.unregisterNetworkCallback(networkCallback)
73 | registered = false
74 | currentLinkProperties = null
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/net/monitor/FallbackUpstreamMonitor.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.net.monitor
2 |
3 | import android.content.SharedPreferences
4 | import com.jtun.router.App.Companion.app
5 | import kotlinx.coroutines.GlobalScope
6 | import kotlinx.coroutines.launch
7 |
8 | abstract class FallbackUpstreamMonitor private constructor() : UpstreamMonitor() {
9 | companion object : SharedPreferences.OnSharedPreferenceChangeListener {
10 | const val KEY = "service.upstream.fallback"
11 |
12 | init {
13 | app.pref.registerOnSharedPreferenceChangeListener(this)
14 | }
15 |
16 | private fun generateMonitor(): UpstreamMonitor {
17 | val upstream = app.pref.getString(KEY, null)
18 | return if (upstream.isNullOrEmpty()) DefaultNetworkMonitor else InterfaceMonitor(upstream)
19 | }
20 | private var monitor = generateMonitor()
21 | val currentNetwork get() = monitor.currentNetwork
22 |
23 | fun registerCallback(callback: Callback) = synchronized(this) { monitor.registerCallback(callback) }
24 | fun unregisterCallback(callback: Callback) = synchronized(this) { monitor.unregisterCallback(callback) }
25 |
26 | override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
27 | if (key == KEY) GlobalScope.launch { // prevent callback called in main
28 | synchronized(this) {
29 | val old = monitor
30 | val callbacks = synchronized(old) {
31 | old.callbacks.toList().also {
32 | old.callbacks.clear()
33 | old.destroyLocked()
34 | }
35 | }
36 | val new = generateMonitor()
37 | monitor = new
38 | for (callback in callbacks) new.registerCallback(callback)
39 | }
40 | }
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/net/monitor/UpstreamMonitor.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.net.monitor
2 |
3 | import android.content.SharedPreferences
4 | import android.net.LinkProperties
5 | import android.net.Network
6 | import com.jtun.router.App.Companion.app
7 | import kotlinx.coroutines.GlobalScope
8 | import kotlinx.coroutines.launch
9 |
10 | abstract class UpstreamMonitor {
11 | companion object : SharedPreferences.OnSharedPreferenceChangeListener {
12 | const val KEY = "service.upstream"
13 |
14 | init {
15 | app.pref.registerOnSharedPreferenceChangeListener(this)
16 | }
17 |
18 | private fun generateMonitor(): UpstreamMonitor {
19 | val upstream = app.pref.getString(KEY, null)
20 | return if (upstream.isNullOrEmpty()) VpnMonitor else InterfaceMonitor(upstream)
21 | }
22 | private var monitor = generateMonitor()
23 | val currentNetwork get() = monitor.currentNetwork
24 |
25 | fun registerCallback(callback: Callback) = synchronized(this) { monitor.registerCallback(callback) }
26 | fun unregisterCallback(callback: Callback) = synchronized(this) { monitor.unregisterCallback(callback) }
27 |
28 | override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
29 | if (key == KEY) GlobalScope.launch { // prevent callback called in main
30 | synchronized(this) {
31 | val old = monitor
32 | val callbacks = synchronized(old) {
33 | old.callbacks.toList().also {
34 | old.callbacks.clear()
35 | old.destroyLocked()
36 | }
37 | }
38 | val new = generateMonitor()
39 | monitor = new
40 | for (callback in callbacks) new.registerCallback(callback)
41 | }
42 | }
43 | }
44 | }
45 |
46 | interface Callback {
47 | /**
48 | * Called if some possibly stacked interface is available
49 | */
50 | fun onAvailable(properties: LinkProperties? = null) { }
51 | }
52 |
53 | val callbacks = mutableSetOf()
54 | var currentNetwork: Network? = null
55 | protected set
56 | protected abstract val currentLinkProperties: LinkProperties?
57 | protected abstract fun registerCallbackLocked(callback: Callback)
58 | abstract fun destroyLocked()
59 |
60 | fun registerCallback(callback: Callback) {
61 | synchronized(this) {
62 | if (callbacks.add(callback)) registerCallbackLocked(callback)
63 | }
64 | }
65 | fun unregisterCallback(callback: Callback) = synchronized(this) {
66 | if (callbacks.remove(callback) && callbacks.isEmpty()) destroyLocked()
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/net/monitor/VpnMonitor.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.net.monitor
2 |
3 | import android.net.ConnectivityManager
4 | import android.net.LinkProperties
5 | import android.net.Network
6 | import android.net.NetworkCapabilities
7 | import com.jtun.router.net.VpnFirewallManager
8 | import com.jtun.router.util.Services
9 | import com.jtun.router.util.globalNetworkRequestBuilder
10 | import kotlinx.coroutines.GlobalScope
11 | import kotlinx.coroutines.launch
12 | import timber.log.Timber
13 |
14 | object VpnMonitor : UpstreamMonitor() {
15 | private val request = globalNetworkRequestBuilder().apply {
16 | addTransportType(NetworkCapabilities.TRANSPORT_VPN)
17 | removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
18 | }.build()
19 | private var registered = false
20 |
21 | private val available = HashMap()
22 | override val currentLinkProperties: LinkProperties? get() = currentNetwork?.let { available[it] }
23 | private val networkCallback = object : ConnectivityManager.NetworkCallback() {
24 | private fun fireCallbacks(properties: LinkProperties?, callbacks: Iterable) = GlobalScope.launch {
25 | if (properties != null) VpnFirewallManager.excludeIfNeeded(this)
26 | callbacks.forEach { it.onAvailable(properties) }
27 | }
28 |
29 | override fun onAvailable(network: Network) {
30 | val properties = Services.connectivity.getLinkProperties(network)
31 | fireCallbacks(properties, synchronized(this@VpnMonitor) {
32 | available[network] = properties
33 | currentNetwork = network
34 | callbacks.toList()
35 | })
36 | }
37 |
38 | override fun onLinkPropertiesChanged(network: Network, properties: LinkProperties) {
39 | fireCallbacks(properties, synchronized(this@VpnMonitor) {
40 | available[network] = properties
41 | if (currentNetwork == null) currentNetwork = network
42 | else if (currentNetwork != network) return
43 | callbacks.toList()
44 | })
45 | }
46 |
47 | override fun onLost(network: Network) {
48 | var properties: LinkProperties? = null
49 | val callbacks = synchronized(this@VpnMonitor) {
50 | if (available.remove(network) == null || currentNetwork != network) return
51 | if (available.isNotEmpty()) {
52 | val next = available.entries.first()
53 | currentNetwork = next.key
54 | Timber.d("Switching to ${next.value} as VPN interface")
55 | properties = next.value
56 | } else currentNetwork = null
57 | callbacks.toList()
58 | }
59 | fireCallbacks(properties, callbacks)
60 | }
61 | }
62 |
63 | override fun registerCallbackLocked(callback: Callback) {
64 | if (registered) {
65 | val currentLinkProperties = currentLinkProperties
66 | if (currentLinkProperties != null) GlobalScope.launch {
67 | callback.onAvailable(currentLinkProperties)
68 | }
69 | } else {
70 | Services.registerNetworkCallback(request, networkCallback)
71 | registered = true
72 | }
73 | }
74 |
75 | override fun destroyLocked() {
76 | if (!registered) return
77 | Services.connectivity.unregisterNetworkCallback(networkCallback)
78 | registered = false
79 | available.clear()
80 | currentNetwork = null
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/net/wifi/SoftApCapability.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.net.wifi
2 |
3 | import android.annotation.TargetApi
4 | import android.os.Build
5 | import android.os.Parcelable
6 | import androidx.annotation.RequiresApi
7 | import com.jtun.router.util.LongConstantLookup
8 | import com.jtun.router.util.UnblockCentral
9 | import timber.log.Timber
10 |
11 | @JvmInline
12 | @RequiresApi(30)
13 | value class SoftApCapability(val inner: Parcelable) {
14 | companion object {
15 | val clazz by lazy { Class.forName("android.net.wifi.SoftApCapability") }
16 | private val getMaxSupportedClients by lazy { clazz.getDeclaredMethod("getMaxSupportedClients") }
17 | private val areFeaturesSupported by lazy { clazz.getDeclaredMethod("areFeaturesSupported", Long::class.java) }
18 | @get:RequiresApi(31)
19 | private val getSupportedChannelList by lazy {
20 | clazz.getDeclaredMethod("getSupportedChannelList", Int::class.java)
21 | }
22 | @get:RequiresApi(31)
23 | @get:TargetApi(33)
24 | private val getCountryCode by lazy { UnblockCentral.getCountryCode(clazz) }
25 |
26 | @RequiresApi(31)
27 | const val SOFTAP_FEATURE_BAND_24G_SUPPORTED = 32L
28 | @RequiresApi(31)
29 | const val SOFTAP_FEATURE_BAND_5G_SUPPORTED = 64L
30 | @RequiresApi(31)
31 | const val SOFTAP_FEATURE_BAND_6G_SUPPORTED = 128L
32 | @RequiresApi(31)
33 | const val SOFTAP_FEATURE_BAND_60G_SUPPORTED = 256L
34 | val featureLookup by lazy { LongConstantLookup(clazz, "SOFTAP_FEATURE_") }
35 | }
36 |
37 | val maxSupportedClients get() = getMaxSupportedClients(inner) as Int
38 | val supportedFeatures: Long get() {
39 | var supportedFeatures = 0L
40 | var probe = 1L
41 | while (probe != 0L) {
42 | if (areFeaturesSupported(inner, probe) as Boolean) supportedFeatures = supportedFeatures or probe
43 | probe += probe
44 | }
45 | return supportedFeatures
46 | }
47 | fun getSupportedChannelList(band: Int) = getSupportedChannelList(inner, band) as IntArray
48 | @get:RequiresApi(31)
49 | val countryCode: String? get() = try {
50 | getCountryCode(inner) as String?
51 | } catch (e: ReflectiveOperationException) {
52 | if (Build.VERSION.SDK_INT >= 33) Timber.w(e)
53 | null
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/net/wifi/SoftApInfo.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.net.wifi
2 |
3 | import android.annotation.TargetApi
4 | import android.net.MacAddress
5 | import android.os.Parcelable
6 | import androidx.annotation.RequiresApi
7 | import com.jtun.router.util.ConstantLookup
8 | import com.jtun.router.util.UnblockCentral
9 | import timber.log.Timber
10 |
11 | @JvmInline
12 | @RequiresApi(30)
13 | value class SoftApInfo(val inner: Parcelable) {
14 | companion object {
15 | val clazz by lazy { Class.forName("android.net.wifi.SoftApInfo") }
16 | private val getFrequency by lazy { clazz.getDeclaredMethod("getFrequency") }
17 | private val getBandwidth by lazy { clazz.getDeclaredMethod("getBandwidth") }
18 | @get:RequiresApi(31)
19 | private val getBssid by lazy { clazz.getDeclaredMethod("getBssid") }
20 | @get:RequiresApi(31)
21 | private val getWifiStandard by lazy { clazz.getDeclaredMethod("getWifiStandard") }
22 | @get:RequiresApi(31)
23 | private val getApInstanceIdentifier by lazy @TargetApi(31) { UnblockCentral.getApInstanceIdentifier(clazz) }
24 | @get:RequiresApi(31)
25 | private val getAutoShutdownTimeoutMillis by lazy { clazz.getDeclaredMethod("getAutoShutdownTimeoutMillis") }
26 |
27 | val channelWidthLookup = ConstantLookup("CHANNEL_WIDTH_") { clazz }
28 | }
29 |
30 | val frequency get() = getFrequency(inner) as Int
31 | val bandwidth get() = getBandwidth(inner) as Int
32 | @get:RequiresApi(31)
33 | val bssid get() = getBssid(inner) as MacAddress?
34 | @get:RequiresApi(31)
35 | val wifiStandard get() = getWifiStandard(inner) as Int
36 | @get:RequiresApi(31)
37 | val apInstanceIdentifier get() = try {
38 | getApInstanceIdentifier(inner) as? String
39 | } catch (e: ReflectiveOperationException) {
40 | Timber.w(e)
41 | null
42 | }
43 | @get:RequiresApi(31)
44 | val autoShutdownTimeoutMillis get() = getAutoShutdownTimeoutMillis(inner) as Long
45 | }
46 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/net/wifi/VendorElements.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.net.wifi
2 |
3 | import android.net.wifi.ScanResult
4 | import androidx.annotation.RequiresApi
5 | import timber.log.Timber
6 |
7 | @RequiresApi(33)
8 | object VendorElements {
9 | fun serialize(input: List) = input.joinToString("\n") { element ->
10 | element.bytes.let { buffer ->
11 | StringBuilder().apply {
12 | @OptIn(ExperimentalStdlibApi::class)
13 | while (buffer.hasRemaining()) append(buffer.get().toHexString())
14 | }.toString()
15 | }.also {
16 | if (element.id != 221 || element.idExt != 0 || it.isEmpty()) Timber.w(Exception(
17 | "Unexpected InformationElement ${element.id}, ${element.idExt}, $it"))
18 | }
19 | }
20 |
21 | fun deserialize(input: CharSequence?) = (input ?: "").split("\n").mapNotNull { line ->
22 | @OptIn(ExperimentalStdlibApi::class)
23 | if (line.isBlank()) null else ScanResult.InformationElement(221, 0, line.hexToByteArray())
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/net/wifi/WifiClient.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.net.wifi
2 |
3 | import android.annotation.TargetApi
4 | import android.net.MacAddress
5 | import android.os.Parcelable
6 | import androidx.annotation.RequiresApi
7 | import com.jtun.router.util.UnblockCentral
8 | import timber.log.Timber
9 |
10 | @JvmInline
11 | @RequiresApi(30)
12 | value class WifiClient(val inner: Parcelable) {
13 | companion object {
14 | val clazz by lazy { Class.forName("android.net.wifi.WifiClient") }
15 | private val getMacAddress by lazy { clazz.getDeclaredMethod("getMacAddress") }
16 | @get:RequiresApi(31)
17 | private val getApInstanceIdentifier by lazy @TargetApi(31) { UnblockCentral.getApInstanceIdentifier(clazz) }
18 | }
19 |
20 | val macAddress get() = getMacAddress(inner) as MacAddress
21 | @get:RequiresApi(31)
22 | val apInstanceIdentifier get() = try {
23 | getApInstanceIdentifier(inner) as? String
24 | } catch (e: ReflectiveOperationException) {
25 | Timber.w(e)
26 | null
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/net/wifi/WifiSsidCompat.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.net.wifi
2 |
3 | import android.net.wifi.WifiSsid
4 | import android.os.Parcelable
5 | import androidx.annotation.RequiresApi
6 | import kotlinx.parcelize.Parcelize
7 | import org.jetbrains.annotations.Contract
8 | import java.nio.ByteBuffer
9 | import java.nio.CharBuffer
10 | import java.nio.charset.Charset
11 | import java.nio.charset.CodingErrorAction
12 |
13 | @Parcelize
14 | data class WifiSsidCompat(val bytes: ByteArray) : Parcelable {
15 | companion object {
16 | private val hexTester = Regex("^(?:[0-9a-f]{2})*$", RegexOption.IGNORE_CASE)
17 | private val qrSanitizer = Regex("([\\\\\":;,])")
18 |
19 | @OptIn(ExperimentalStdlibApi::class)
20 | fun fromHex(hex: String?) = hex?.run { WifiSsidCompat(hexToByteArray()) }
21 |
22 | @Contract("null -> null; !null -> !null")
23 | fun fromUtf8Text(text: String?, truncate: Boolean = false) = text?.toByteArray()?.let {
24 | WifiSsidCompat(if (truncate && it.size > 32) it.sliceArray(0 until 32) else it)
25 | }
26 |
27 | fun toMeCard(text: String) = qrSanitizer.replace(text) { "\\${it.groupValues[1]}" }
28 |
29 | @RequiresApi(33)
30 | fun WifiSsid.toCompat() = WifiSsidCompat(bytes)
31 | }
32 |
33 | init {
34 | require(bytes.size <= 32) { "${bytes.size} > 32" }
35 | }
36 |
37 | @RequiresApi(31)
38 | fun toPlatform() = WifiSsid.fromBytes(bytes)
39 |
40 | fun decode(charset: Charset = Charsets.UTF_8) = CharBuffer.allocate(32).run {
41 | val result = charset.newDecoder().apply {
42 | onMalformedInput(CodingErrorAction.REPORT)
43 | onUnmappableCharacter(CodingErrorAction.REPORT)
44 | }.decode(ByteBuffer.wrap(bytes), this, true)
45 | if (result.isError) null else flip().toString()
46 | }
47 | @OptIn(ExperimentalStdlibApi::class)
48 | val hex get() = bytes.toHexString()
49 |
50 | fun toMeCard(): String {
51 | val utf8 = decode() ?: return hex
52 | return if (hexTester.matches(utf8)) "\"$utf8\"" else toMeCard(utf8)
53 | }
54 |
55 | override fun toString() = String(bytes)
56 |
57 | override fun equals(other: Any?): Boolean {
58 | if (this === other) return true
59 | if (javaClass != other?.javaClass) return false
60 | other as WifiSsidCompat
61 | if (!bytes.contentEquals(other.bytes)) return false
62 | return true
63 | }
64 |
65 | override fun hashCode() = bytes.contentHashCode()
66 | }
67 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/receiver/BootCompletedReceiver.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.receiver
2 |
3 | import android.app.ActivityManager
4 | import android.content.BroadcastReceiver
5 | import android.content.ComponentName
6 | import android.content.Context
7 | import android.content.Intent
8 | import com.jtun.router.util.ApkUtils
9 | import com.jtun.router.util.KLog
10 |
11 | class BootCompletedReceiver : BroadcastReceiver() {
12 | private fun startAct(context: Context, cn: ComponentName) {
13 | val packageName: String = ApkUtils.getTopPackageName(context)
14 | val isRun = isPackageRunning(context, cn.packageName)
15 | KLog.i(cn.packageName + " isRun : " + isRun)
16 | KLog.i("top package : $packageName")
17 | if (packageName == cn.packageName) {
18 | return
19 | }
20 | val intent = Intent(Intent.ACTION_MAIN)
21 | intent.addCategory(Intent.CATEGORY_LAUNCHER)
22 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
23 |
24 | try {
25 | intent.setComponent(cn)
26 | context.startActivity(intent)
27 | } catch (e: Exception) {
28 | KLog.i(e.message)
29 | }
30 | }
31 |
32 | /**
33 | * 根据报名返回该进程是否启动
34 | * @param context 上下文
35 | * @param packagename 包名
36 | * @return
37 | */
38 | private fun isPackageRunning(context: Context, packagename: String?): Boolean {
39 | return findPIDbyPackageName(context, packagename) != -1
40 | }
41 |
42 | /**
43 | * 根据报名查找指定pid
44 | * @param context 上下文
45 | * @param packagename 包名
46 | * @return pid
47 | */
48 | private fun findPIDbyPackageName(context: Context, packagename: String?): Int {
49 | val am = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
50 | var result = -1
51 | if (am != null) {
52 | for (pi in am.runningAppProcesses) {
53 | if (pi.processName.equals(packagename, ignoreCase = true)) {
54 | result = pi.pid
55 | }
56 | if (result != -1) break
57 | }
58 | } else {
59 | result = -1
60 | }
61 |
62 | return result
63 | }
64 |
65 | override fun onReceive(context: Context, intent: Intent?) {
66 | KLog.i("接收到开机广播")
67 | /**
68 | * 主程序
69 | */
70 | // val pkgName = context.packageName
71 | // val clsName = "com.jtun.router" + ".MainActivity"
72 | // val cn = ComponentName(pkgName, clsName)
73 | // KLog.w(null, "ComponentName:$cn")
74 | // startAct(context, cn)
75 |
76 | }
77 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/room/AppDatabase.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.room
2 |
3 | import androidx.room.Database
4 | import androidx.room.Room
5 | import androidx.room.RoomDatabase
6 | import androidx.room.TypeConverters
7 | import com.jtun.router.App.Companion.app
8 | import kotlinx.coroutines.GlobalScope
9 | import kotlinx.coroutines.launch
10 |
11 | @Database(entities = [ClientRecord::class,TrafficRecord::class,ClientConnected::class,AppInfo::class,Log::class], version = 6)
12 | @TypeConverters(Converters::class)
13 | abstract class AppDatabase : RoomDatabase() {
14 | companion object {
15 | const val DB_NAME = "app.db"
16 |
17 | val instance by lazy {
18 | Room.databaseBuilder(app.deviceStorage, AppDatabase::class.java, DB_NAME).apply {
19 | fallbackToDestructiveMigration()
20 | setQueryExecutor { GlobalScope.launch { it.run() } }
21 | }.build()
22 | }
23 | }
24 |
25 | abstract val clientRecordDao: ClientRecord.Dao
26 | abstract val trafficRecordDao: TrafficRecord.Dao
27 | abstract val clientDao: ClientDao
28 | abstract val appDao : AppInfo.Dao
29 | abstract val logDao : Log.Dao
30 | }
31 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/room/AppInfo.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.room
2 |
3 | import androidx.room.Entity
4 | import androidx.room.Insert
5 | import androidx.room.OnConflictStrategy
6 | import androidx.room.PrimaryKey
7 | import androidx.room.Query
8 | import com.dolphin.localsocket.bean.PackageInfo
9 | import com.jtun.router.util.KLog
10 |
11 | @Entity
12 | data class AppInfo(@PrimaryKey
13 | val packageName:String,
14 | val canonicalName :String?,
15 | val versionCode:Long,
16 | val versionName : String,
17 | val name:String,
18 | val brief:String,
19 | var isRun :Boolean = true){
20 | companion object{
21 | fun AppInfo.toCompat() = PackageInfo(packageName,canonicalName,versionCode,versionName,name,brief,isRun)
22 | fun PackageInfo.toCompat() = AppInfo(packageName,canonicalName,versionCode,versionName,name,brief,isRun)
23 | }
24 |
25 | @androidx.room.Dao
26 | abstract class Dao {
27 | @Query("SELECT * FROM `AppInfo` WHERE `packageName` = :packageName")
28 | protected abstract suspend fun lookup(packageName: String): AppInfo?
29 | suspend fun lookupOrDefault(packageName: String) = lookup(packageName)
30 |
31 | @Insert(onConflict = OnConflictStrategy.REPLACE)
32 | protected abstract suspend fun updateInternal(value: AppInfo): Long
33 | suspend fun update(value: AppInfo) {
34 | val result = updateInternal(value)
35 | KLog.i("updateInternal result : $result")
36 | }
37 |
38 | @Query("SELECT * FROM `AppInfo`")
39 | protected abstract suspend fun selectList(): List
40 | suspend fun getList() = selectList()
41 |
42 | @Query("DELETE FROM `AppInfo` WHERE `packageName` = :packageName")
43 | protected abstract suspend fun delete(packageName: String)
44 | suspend fun deleteApp(packageName: String) {
45 | delete(packageName)
46 | }
47 | }
48 | }
49 |
50 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/room/ClientConnected.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.room
2 |
3 | import androidx.room.Entity
4 | import androidx.room.PrimaryKey
5 |
6 | @Entity
7 | data class ClientConnected(@PrimaryKey
8 | val mac: String,//mac
9 | var nickname: String = "",//名称
10 | var ipAddress : String = "",//ip地址
11 | var linkTime:Long = 0,//连接时间
12 | var allowInternet:Boolean = true, //是否允许连接网络
13 | var onLine : Boolean = false){ //是否在线
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/room/ClientDao.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.room
2 |
3 | import androidx.room.Insert
4 | import androidx.room.OnConflictStrategy
5 | import androidx.room.Query
6 | import androidx.room.Transaction
7 | import com.jtun.router.util.KLog
8 |
9 | @androidx.room.Dao
10 | abstract class ClientDao {
11 | @Query("SELECT * FROM `ClientConnected` WHERE `mac` = :mac")
12 | protected abstract suspend fun lookup(mac: String): ClientConnected?
13 | suspend fun lookupOrDefault(mac: String) = lookup(mac) ?: ClientConnected(mac)
14 |
15 | @Insert(onConflict = OnConflictStrategy.REPLACE)
16 | protected abstract suspend fun updateInternal(value: ClientConnected): Long
17 | suspend fun update(value: ClientConnected) {
18 | val result = updateInternal(value)
19 | KLog.i("updateInternal result : $result")
20 | }
21 |
22 | @Query("SELECT * FROM `ClientConnected`")
23 | protected abstract suspend fun selectList(): List
24 | suspend fun getList() = selectList()
25 |
26 | @Query("DELETE FROM `ClientConnected` WHERE `mac` = :mac")
27 | protected abstract suspend fun deleteClient(mac: String)
28 | suspend fun deleteClientMac(mac: String) {
29 | deleteClient(mac)
30 | }
31 |
32 | @Transaction
33 | open suspend fun upsert(mac: String, operation: suspend ClientConnected.() -> Unit) = lookupOrDefault(mac).apply {
34 | operation()
35 | update(this)
36 | }
37 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/room/ClientRecord.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.room
2 |
3 | import android.net.MacAddress
4 | import androidx.lifecycle.LiveData
5 | import androidx.lifecycle.map
6 | import androidx.room.*
7 | import com.jtun.router.net.MacAddressCompat.Companion.toLong
8 |
9 | @Entity
10 | data class ClientRecord(@PrimaryKey
11 | val mac: MacAddress,
12 | var nickname: CharSequence = "",
13 | var blocked: Boolean = false,
14 | var macLookupPending: Boolean = true) {
15 | @androidx.room.Dao
16 | abstract class Dao {
17 | @Query("SELECT * FROM `ClientRecord` WHERE `mac` = :mac")
18 | protected abstract fun lookupBlocking(mac: MacAddress): ClientRecord?
19 | fun lookupOrDefaultBlocking(mac: MacAddress) = lookupBlocking(mac) ?: ClientRecord(mac)
20 |
21 | @Query("SELECT * FROM `ClientRecord` WHERE `mac` = :mac")
22 | protected abstract suspend fun lookup(mac: MacAddress): ClientRecord?
23 | suspend fun lookupOrDefault(mac: MacAddress) = lookup(mac) ?: ClientRecord(mac)
24 |
25 | @Query("SELECT * FROM `ClientRecord` WHERE `mac` = :mac")
26 | protected abstract fun lookupSync(mac: MacAddress): LiveData
27 | fun lookupOrDefaultSync(mac: MacAddress) = lookupSync(mac).map { it ?: ClientRecord(mac) }
28 |
29 | @Insert(onConflict = OnConflictStrategy.REPLACE)
30 | protected abstract suspend fun updateInternal(value: ClientRecord): Long
31 | suspend fun update(value: ClientRecord) = check(updateInternal(value) == value.mac.toLong())
32 |
33 | @Transaction
34 | open suspend fun upsert(mac: MacAddress, operation: suspend ClientRecord.() -> Unit) = lookupOrDefault(
35 | mac).apply {
36 | operation()
37 | update(this)
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/room/Converters.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.room
2 |
3 | import android.net.MacAddress
4 | import android.text.TextUtils
5 | import androidx.room.TypeConverter
6 | import be.mygod.librootkotlinx.useParcel
7 | import com.jtun.router.net.MacAddressCompat
8 | import com.jtun.router.net.MacAddressCompat.Companion.toLong
9 | import timber.log.Timber
10 | import java.net.InetAddress
11 |
12 | object Converters {
13 | @JvmStatic
14 | @TypeConverter
15 | fun persistCharSequence(cs: CharSequence) = useParcel { p ->
16 | TextUtils.writeToParcel(cs, p, 0)
17 | p.marshall()
18 | }
19 |
20 | @JvmStatic
21 | @TypeConverter
22 | fun unpersistCharSequence(data: ByteArray) = useParcel { p ->
23 | p.unmarshall(data, 0, data.size)
24 | p.setDataPosition(0)
25 | try {
26 | TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(p)!!
27 | } catch (e: RuntimeException) {
28 | Timber.w(e)
29 | ""
30 | }
31 | }
32 |
33 | @JvmStatic
34 | @TypeConverter
35 | fun persistMacAddress(address: MacAddress) = address.toLong()
36 |
37 | @JvmStatic
38 | @TypeConverter
39 | fun unpersistMacAddress(address: Long) = MacAddressCompat(address).toPlatform()
40 |
41 | @JvmStatic
42 | @TypeConverter
43 | fun persistInetAddress(address: InetAddress): ByteArray = address.address
44 |
45 | @JvmStatic
46 | @TypeConverter
47 | fun unpersistInetAddress(data: ByteArray): InetAddress = InetAddress.getByAddress(data)
48 | }
49 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/room/Log.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.room
2 |
3 | import androidx.room.Entity
4 | import androidx.room.Insert
5 | import androidx.room.OnConflictStrategy
6 | import androidx.room.PrimaryKey
7 | import androidx.room.Query
8 |
9 | @Entity
10 | data class Log(val type:Int,
11 | val tag:String,
12 | val msg:String,
13 | val time:Long,
14 | @PrimaryKey(autoGenerate = true)
15 | val id:Long = 0,){
16 | companion object{
17 | const val MAX_ITEMS = 20000
18 | }
19 | @androidx.room.Dao
20 | abstract class Dao {
21 | @Insert(onConflict = OnConflictStrategy.REPLACE)
22 | protected abstract suspend fun updateInternal(value: Log): Long
23 | suspend fun update(value: Log) {
24 | //判断是否超过指定数量,如果超过先删除历史的
25 | deleteMaxLog(MAX_ITEMS)
26 | val result = updateInternal(value)
27 | }
28 | @Query("SELECT * FROM `Log` order by time desc limit :limit offset :offset")
29 | protected abstract suspend fun selectList(limit: Int, offset: Int): List
30 | suspend fun getList(limit: Int, offset: Int) = selectList(limit,offset)
31 |
32 | @Query("SELECT * FROM `Log` order by time desc")
33 | protected abstract suspend fun selectList(): List
34 | suspend fun getList() = selectList()
35 |
36 | @Query("SELECT * FROM `Log` WHERE type=:type order by time desc limit :limit offset :offset")
37 | protected abstract suspend fun selectList(type: Int, limit: Int, offset: Int): List
38 | suspend fun getList(type: Int, limit: Int, offset: Int) = selectList(type,limit,offset)
39 |
40 | @Query("SELECT * FROM `Log` WHERE type=:type AND id > :id order by time desc limit :limit")
41 | abstract suspend fun refreshListById(type: Int, limit: Int, id: Long): List
42 |
43 | @Query("SELECT * FROM `Log` WHERE type=:type AND id < :id order by time desc limit :limit")
44 | abstract suspend fun moreListById(type: Int, limit: Int, id: Long): List
45 |
46 | /**
47 | * 删除超过最大数量的日志
48 | */
49 | @Query("delete from `Log` where (select count(id) from `Log`) > :max and id in (select id from `Log` order by time desc limit (select count(id) from `Log`) offset :max )")
50 | protected abstract suspend fun deleteMaxLog(max:Int)
51 | }
52 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/room/TrafficRecord.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.room
2 |
3 | import android.net.MacAddress
4 | import android.os.Parcelable
5 | import androidx.room.*
6 | import kotlinx.parcelize.Parcelize
7 | import java.net.InetAddress
8 |
9 | @Entity(foreignKeys = [ForeignKey(entity = TrafficRecord::class, parentColumns = ["id"], childColumns = ["previousId"],
10 | onDelete = ForeignKey.CASCADE, onUpdate = ForeignKey.RESTRICT)],
11 | indices = [Index(value = ["previousId"], unique = true)])
12 | data class TrafficRecord(
13 | /**
14 | * Setting id = null should only be used when a new row is created and not yet inserted into the database.
15 | *
16 | * https://www.sqlite.org/lang_createtable.html#primkeyconst:
17 | * > Unless the column is an INTEGER PRIMARY KEY or the table is a WITHOUT ROWID table or the column is declared
18 | * > NOT NULL, SQLite allows NULL values in a PRIMARY KEY column.
19 | */
20 | @PrimaryKey(autoGenerate = true)
21 | var id: Long? = null,
22 | val timestamp: Long = System.currentTimeMillis(),
23 | /**
24 | * Foreign key/ID for (possibly non-existent, i.e. default) entry in ClientRecord.
25 | */
26 | val mac: MacAddress,
27 | /**
28 | * For now only stats for IPv4 will be recorded. But I'm going to put the more general class here just in case.
29 | */
30 | val ip: InetAddress,
31 | @Deprecated("This field is no longer used.")
32 | val upstream: String? = null,
33 | val downstream: String,
34 | var sentPackets: Long = 0,
35 | var sentBytes: Long = 0,
36 | var receivedPackets: Long = 0,
37 | var receivedBytes: Long = 0,
38 | /**
39 | * ID for the previous traffic record.
40 | */
41 | val previousId: Long? = null) {
42 | @androidx.room.Dao
43 | abstract class Dao {
44 | @Insert
45 | protected abstract fun insertInternal(value: TrafficRecord): Long
46 | fun insert(value: TrafficRecord) {
47 | check(value.id == null)
48 | value.id = insertInternal(value)
49 | }
50 |
51 | @Query("""
52 | SELECT MIN(TrafficRecord.timestamp) AS timestamp,
53 | COUNT(TrafficRecord.id) AS count,
54 | SUM(TrafficRecord.sentPackets) AS sentPackets,
55 | SUM(TrafficRecord.sentBytes) AS sentBytes,
56 | SUM(TrafficRecord.receivedPackets) AS receivedPackets,
57 | SUM(TrafficRecord.receivedBytes) AS receivedBytes
58 | FROM TrafficRecord LEFT JOIN TrafficRecord AS Next ON TrafficRecord.id = Next.previousId
59 | /* We only want to find the last record for each chain so that we don't double count */
60 | WHERE TrafficRecord.mac = :mac AND Next.id IS NULL
61 | """)
62 | abstract suspend fun queryStats(mac: MacAddress): ClientStats
63 | }
64 | }
65 |
66 | @Parcelize
67 | data class ClientStats(
68 | val timestamp: Long = 0,
69 | val count: Long = 0,
70 | val sentPackets: Long = 0,
71 | val sentBytes: Long = 0,
72 | val receivedPackets: Long = 0,
73 | val receivedBytes: Long = 0
74 | ) : Parcelable
75 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/root/Jni.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.root
2 |
3 | object Jni {
4 | init {
5 | System.loadLibrary("vpnhotspot")
6 | }
7 | external fun removeUidInterfaceRules(path: String?, uid: Int, rules: Long): Boolean
8 | }
9 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/root/LocalOnlyHotspotCallbacks.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.root
2 |
3 | import android.net.wifi.SoftApConfiguration
4 | import android.os.Parcelable
5 | import androidx.annotation.RequiresApi
6 | import kotlinx.parcelize.Parcelize
7 |
8 | @RequiresApi(30)
9 | sealed class LocalOnlyHotspotCallbacks : Parcelable {
10 | @Parcelize
11 | data class OnStarted(val config: SoftApConfiguration) : LocalOnlyHotspotCallbacks()
12 | @Parcelize
13 | class OnStopped : LocalOnlyHotspotCallbacks() {
14 | override fun equals(other: Any?) = other is OnStopped
15 | override fun hashCode() = 0x80acd3ca.toInt()
16 | }
17 | @Parcelize
18 | data class OnFailed(val reason: Int) : LocalOnlyHotspotCallbacks()
19 | }
20 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/root/RootManager.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.root
2 |
3 | import android.annotation.SuppressLint
4 | import android.os.Parcelable
5 | import android.util.Log
6 | import be.mygod.librootkotlinx.*
7 | import com.jtun.router.App.Companion.app
8 | import com.jtun.router.util.Services
9 | import com.jtun.router.util.UnblockCentral
10 | import kotlinx.parcelize.Parcelize
11 | import timber.log.Timber
12 |
13 | object RootManager : RootSession(), Logger {
14 | @Parcelize
15 | class RootInit : RootCommandNoResult {
16 | override suspend fun execute(): Parcelable? {
17 | Timber.plant(object : Timber.DebugTree() {
18 | @SuppressLint("LogNotTimber")
19 | override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
20 | if (priority >= Log.WARN) {
21 | System.err.println("$priority/$tag: $message")
22 | t?.printStackTrace()
23 | }
24 | if (t == null) {
25 | Log.println(priority, tag, message)
26 | } else {
27 | Log.println(priority, tag, message)
28 | Log.d(tag, message, t)
29 | if (priority >= Log.WARN) t.printStackTrace(System.err)
30 | }
31 | }
32 | })
33 | Logger.me = RootManager
34 | Services.init { systemContext }
35 | UnblockCentral.needInit = false
36 | return null
37 | }
38 | }
39 |
40 | override fun d(m: String?, t: Throwable?) = Timber.d(t, m)
41 | override fun e(m: String?, t: Throwable?) = Timber.e(t, m)
42 | override fun i(m: String?, t: Throwable?) = Timber.i(t, m)
43 | override fun w(m: String?, t: Throwable?) = Timber.w(t, m)
44 |
45 | override suspend fun initServer(server: RootServer) {
46 | Logger.me = this
47 | AppProcess.shouldRelocateHeuristics.let {
48 | server.init(app, it)
49 | }
50 | server.execute(RootInit())
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/root/RoutingCommands.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.root
2 |
3 | import android.os.Parcelable
4 | import be.mygod.librootkotlinx.RootCommand
5 | import be.mygod.librootkotlinx.RootCommandNoResult
6 | import com.jtun.router.net.Routing
7 | import kotlinx.coroutines.Dispatchers
8 | import kotlinx.coroutines.async
9 | import kotlinx.coroutines.coroutineScope
10 | import kotlinx.coroutines.withContext
11 | import kotlinx.parcelize.Parcelize
12 | import timber.log.Timber
13 |
14 | object RoutingCommands {
15 |
16 | @Parcelize
17 | class StopApp(val packageName:String) : RootCommandNoResult {
18 | override suspend fun execute() = withContext(Dispatchers.IO) {
19 | val process = ProcessBuilder("sh").fixPath(true).start()
20 | process.outputStream.bufferedWriter().appendLine("am force-stop $packageName")
21 | when (val code = process.waitFor()) {
22 | 0 -> { }
23 | else -> Timber.w("Unexpected exit code $code")
24 | }
25 | check(process.waitFor() == 0)
26 | null
27 | }
28 | }
29 | @Parcelize
30 | class Clean : RootCommandNoResult {
31 | override suspend fun execute() = withContext(Dispatchers.IO) {
32 | val process = ProcessBuilder("sh").fixPath(true).start()
33 | process.outputStream.bufferedWriter().use(Routing.Companion::appendCleanCommands)
34 | when (val code = process.waitFor()) {
35 | 0 -> { }
36 | else -> Timber.w("Unexpected exit code $code")
37 | }
38 | check(process.waitFor() == 0)
39 | null
40 | }
41 | }
42 |
43 | class UnexpectedOutputException(msg: String, val result: ProcessResult) : RuntimeException(msg)
44 |
45 | @Parcelize
46 | data class ProcessResult(val exit: Int, val out: String, val err: String) : Parcelable {
47 | fun message(command: List, out: Boolean = this.out.isNotEmpty(),
48 | err: Boolean = this.err.isNotEmpty()): String? {
49 | val msg = StringBuilder("${command.joinToString(" ")} exited with $exit")
50 | if (out) msg.append("\n${this.out}")
51 | if (err) msg.append("\n=== stderr ===\n${this.err}")
52 | return if (exit != 0 || out || err) msg.toString() else null
53 | }
54 |
55 | fun check(command: List, out: Boolean = this.out.isNotEmpty(),
56 | err: Boolean = this.err.isNotEmpty()) = message(command, out, err)?.let { msg ->
57 | throw UnexpectedOutputException(msg, this)
58 | }
59 | }
60 |
61 | @Parcelize
62 | data class Process(val command: List, private val redirect: Boolean = false) : RootCommand {
63 | @Suppress("BlockingMethodInNonBlockingContext")
64 | override suspend fun execute() = withContext(Dispatchers.IO) {
65 | val process = ProcessBuilder(command).fixPath(redirect).start()
66 | coroutineScope {
67 | val output = async { process.inputStream.bufferedReader().readText() }
68 | val error = async { if (redirect) "" else process.errorStream.bufferedReader().readText() }
69 | ProcessResult(process.waitFor(), output.await(), error.await())
70 | }
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/root/TetheringCommands.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.root
2 |
3 | import android.os.Parcelable
4 | import androidx.annotation.RequiresApi
5 | import be.mygod.librootkotlinx.RootCommandChannel
6 | import com.jtun.router.net.TetheringManager
7 | import kotlinx.coroutines.CompletableDeferred
8 | import kotlinx.coroutines.CoroutineScope
9 | import kotlinx.coroutines.channels.ClosedSendChannelException
10 | import kotlinx.coroutines.channels.onClosed
11 | import kotlinx.coroutines.channels.onFailure
12 | import kotlinx.coroutines.channels.produce
13 | import kotlinx.coroutines.launch
14 | import kotlinx.parcelize.Parcelize
15 |
16 | object TetheringCommands {
17 | /**
18 | * This is the only command supported since other callbacks do not require signature permissions.
19 | */
20 | @Parcelize
21 | data class OnClientsChanged(val clients: List) : Parcelable {
22 | fun dispatch(callback: TetheringManager.TetheringEventCallback) = callback.onClientsChanged(clients)
23 | }
24 |
25 | @Parcelize
26 | @RequiresApi(30)
27 | class RegisterTetheringEventCallback : RootCommandChannel {
28 | override fun create(scope: CoroutineScope) = scope.produce(capacity = capacity) {
29 | val finish = CompletableDeferred()
30 | val callback = object : TetheringManager.TetheringEventCallback {
31 | private fun push(parcel: OnClientsChanged) {
32 | trySend(parcel).onClosed {
33 | finish.completeExceptionally(it ?: ClosedSendChannelException("Channel was closed normally"))
34 | return
35 | }.onFailure { throw it!! }
36 | }
37 |
38 | override fun onClientsChanged(clients: Collection) =
39 | push(OnClientsChanged(clients.toList()))
40 | }
41 | TetheringManager.registerTetheringEventCallback(callback) {
42 | scope.launch {
43 | try {
44 | it.run()
45 | } catch (e: Throwable) {
46 | finish.completeExceptionally(e)
47 | }
48 | }
49 | }
50 | try {
51 | finish.await()
52 | } finally {
53 | TetheringManager.unregisterTetheringEventCallback(callback)
54 | }
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/sms/SMSConversation.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.sms
2 |
3 | data class SMSConversation(val address:String,//发送人地址
4 | val lastMessage:Sms, //最后一条信息
5 | val threadId:Int)//会话id
6 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/sms/Sms.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.sms
2 |
3 | data class Sms(val id:Int,
4 | val address:String,//发件人地址
5 | val person:String?,//如果发件人在通讯录中则为具体姓名,陌生人为null
6 | val body :String?,//短信具体内容
7 | val data:String,//时间戳
8 | val type:Int, //短信类型1是接收到的,2是已发出
9 | val read:Int = 1)//1表示已读 0为未读
10 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/tasker/StateAction.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.tasker
2 |
3 | import android.Manifest
4 | import android.content.Context
5 | import android.content.pm.PackageManager
6 | import android.os.Bundle
7 | import androidx.appcompat.app.AppCompatActivity
8 | import com.jtun.router.TetheringService
9 | import com.joaomgcd.taskerpluginlibrary.action.TaskerPluginRunnerActionNoInput
10 | import com.joaomgcd.taskerpluginlibrary.config.TaskerPluginConfigHelperNoInput
11 | import com.joaomgcd.taskerpluginlibrary.config.TaskerPluginConfigNoInput
12 | import com.joaomgcd.taskerpluginlibrary.input.TaskerInput
13 | import com.joaomgcd.taskerpluginlibrary.runner.TaskerPluginResultErrorWithOutput
14 | import com.joaomgcd.taskerpluginlibrary.runner.TaskerPluginResultSucess
15 |
16 | class GetStateConfig : AppCompatActivity(), TaskerPluginConfigNoInput {
17 | override val context: Context
18 | get() = this
19 |
20 | override fun onCreate(savedInstanceState: Bundle?) {
21 | super.onCreate(savedInstanceState)
22 | GetStateHelper(this).apply {
23 | onCreate()
24 | finishForTasker()
25 | }
26 | }
27 | }
28 |
29 | class GetStateHelper(config: GetStateConfig) : TaskerPluginConfigHelperNoInput(config) {
30 | override val outputClass: Class = TetheringState::class.java
31 | override val runnerClass: Class = GetStateRunner::class.java
32 | }
33 |
34 | class GetStateRunner : TaskerPluginRunnerActionNoInput() {
35 | override fun run(
36 | context: Context,
37 | input: TaskerInput,
38 | ) = if (context.checkCallingPermission(Manifest.permission.ACCESS_NETWORK_STATE) ==
39 | PackageManager.PERMISSION_GRANTED) {
40 | TaskerPluginResultSucess(TetheringState(TetheringService.activeTetherTypes))
41 | } else TaskerPluginResultErrorWithOutput(SecurityException("Need ACCESS_NETWORK_STATE permission"))
42 | }
43 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/tasker/TaskerEvents.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.tasker
2 |
3 | import android.Manifest
4 | import android.content.Context
5 | import android.content.pm.PackageManager
6 | import android.os.Bundle
7 | import androidx.appcompat.app.AppCompatActivity
8 | import com.jtun.router.TetheringService
9 | import com.joaomgcd.taskerpluginlibrary.condition.TaskerPluginRunnerConditionEvent
10 | import com.joaomgcd.taskerpluginlibrary.config.TaskerPluginConfig
11 | import com.joaomgcd.taskerpluginlibrary.config.TaskerPluginConfigHelper
12 | import com.joaomgcd.taskerpluginlibrary.config.TaskerPluginConfigNoInput
13 | import com.joaomgcd.taskerpluginlibrary.input.TaskerInput
14 | import com.joaomgcd.taskerpluginlibrary.runner.TaskerPluginResultConditionSatisfied
15 | import com.joaomgcd.taskerpluginlibrary.runner.TaskerPluginResultConditionUnknown
16 | import timber.log.Timber
17 |
18 | class TetheringEventConfig : AppCompatActivity(), TaskerPluginConfigNoInput {
19 | override val context: Context
20 | get() = this
21 |
22 | override fun onCreate(savedInstanceState: Bundle?) {
23 | super.onCreate(savedInstanceState)
24 | TetheringEventHelper(this).apply {
25 | onCreate()
26 | finishForTasker()
27 | }
28 | }
29 | }
30 |
31 | class TetheringEventHelper(config: TaskerPluginConfig) : TaskerPluginConfigHelper(config) {
32 | override val runnerClass: Class = TetheringEventRunner::class.java
33 | override val inputClass: Class = Unit::class.java
34 | override val outputClass: Class = TetheringState::class.java
35 | }
36 |
37 | class TetheringEventRunner : TaskerPluginRunnerConditionEvent() {
38 | override fun getSatisfiedCondition(
39 | context: Context,
40 | input: TaskerInput,
41 | update: Unit?,
42 | ) = if (context.checkCallingPermission(Manifest.permission.ACCESS_NETWORK_STATE) ==
43 | PackageManager.PERMISSION_GRANTED) {
44 | TaskerPluginResultConditionSatisfied(
45 | context = context,
46 | regular = TetheringState(TetheringService.activeTetherTypes),
47 | )
48 | } else {
49 | Timber.w("TetheringEventRunner needs ACCESS_NETWORK_STATE permission")
50 | TaskerPluginResultConditionUnknown()
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/tasker/TetheringState.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.tasker
2 |
3 | import com.jtun.router.net.TetherType
4 | import com.joaomgcd.taskerpluginlibrary.output.TaskerOutputObject
5 | import com.joaomgcd.taskerpluginlibrary.output.TaskerOutputVariable
6 |
7 | @TaskerOutputObject
8 | class TetheringState(
9 | @get:TaskerOutputVariable("wifi")
10 | val wifi: Boolean = false,
11 | @get:TaskerOutputVariable("bluetooth")
12 | val bluetooth: Boolean = false,
13 | @get:TaskerOutputVariable("usb")
14 | val usb: Boolean = false,
15 | @get:TaskerOutputVariable("ethernet")
16 | val ethernet: Boolean = false,
17 | ) {
18 | companion object {
19 | operator fun invoke(types: Set): TetheringState {
20 | return TetheringState(
21 | wifi = types.contains(TetherType.WIFI),
22 | bluetooth = types.contains(TetherType.BLUETOOTH),
23 | usb = types.contains(TetherType.USB) || types.contains(TetherType.NCM),
24 | ethernet = types.contains(TetherType.ETHERNET),
25 | )
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/util/AppUpdate.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.util
2 |
3 | import android.app.Activity
4 |
5 | interface AppUpdate {
6 | class IgnoredException(cause: Throwable?) : RuntimeException(cause)
7 |
8 | val downloaded: Boolean? get() = null
9 | val message: String? get() = null
10 | val stalenessDays: Int? get() = null
11 | fun updateForResult(activity: Activity, requestCode: Int): Unit = error("Update not supported")
12 | }
13 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/util/ConstantLookup.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.util
2 |
3 | import android.os.Build
4 | import androidx.collection.LongSparseArray
5 | import androidx.collection.SparseArrayCompat
6 | import com.jtun.router.App.Companion.app
7 | import com.jtun.router.R
8 | import timber.log.Timber
9 |
10 | class ConstantLookup(private val prefix: String, private val lookup29: Array,
11 | private val clazz: () -> Class<*>) {
12 | val lookup by lazy {
13 | SparseArrayCompat().apply {
14 | for (field in clazz().declaredFields) try {
15 | if (field?.type == Int::class.java && field.name.startsWith(prefix)) put(field.getInt(null), field.name)
16 | } catch (e: Exception) {
17 | Timber.w(e)
18 | }
19 | }
20 | }
21 |
22 | operator fun invoke(reason: Int, trimPrefix: Boolean = false): String {
23 | if (Build.VERSION.SDK_INT >= 30) try {
24 | lookup.get(reason)?.let { return if (trimPrefix) it.substring(prefix.length) else it }
25 | } catch (e: ReflectiveOperationException) {
26 | Timber.w(e)
27 | }
28 | return lookup29.getOrNull(reason)?.let { if (trimPrefix) it else prefix + it }
29 | ?: app.getString(R.string.failure_reason_unknown, reason)
30 | }
31 | }
32 |
33 | fun ConstantLookup(prefix: String, vararg lookup29: String?, clazz: () -> Class<*>) =
34 | ConstantLookup(prefix, lookup29, clazz)
35 | inline fun ConstantLookup(prefix: String, vararg lookup29: String?) =
36 | ConstantLookup(prefix, lookup29) { T::class.java }
37 |
38 | class LongConstantLookup(private val clazz: Class<*>, private val prefix: String) {
39 | private val lookup = LongSparseArray().apply {
40 | for (field in clazz.declaredFields) try {
41 | if (field?.type == Long::class.java && field.name.startsWith(prefix)) put(field.getLong(null), field.name)
42 | } catch (e: Exception) {
43 | Timber.w(e)
44 | }
45 | }
46 |
47 | operator fun invoke(reason: Long, trimPrefix: Boolean = false): String {
48 | try {
49 | lookup.get(reason)?.let { return if (trimPrefix) it.substring(prefix.length) else it }
50 | } catch (e: ReflectiveOperationException) {
51 | Timber.w(e)
52 | }
53 | return app.getString(R.string.failure_reason_unknown, reason)
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/util/CustomTabsUrlSpan.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.util
2 |
3 | import android.text.style.URLSpan
4 | import android.view.View
5 |
6 | class CustomTabsUrlSpan(url: String) : URLSpan(url) {
7 | override fun onClick(widget: View) = widget.context.launchUrl(url)
8 | }
9 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/util/DeviceStorageApp.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.util
2 |
3 | import android.annotation.SuppressLint
4 | import android.app.Application
5 | import android.content.ComponentCallbacks
6 | import android.content.Context
7 | import android.content.res.Configuration
8 |
9 | @SuppressLint("MissingSuperCall", "Registered")
10 | class DeviceStorageApp(private val app: Application) : Application() {
11 | init {
12 | attachBaseContext(app.createDeviceProtectedStorageContext())
13 | }
14 |
15 | /**
16 | * Thou shalt not get the REAL underlying application context which would no longer be operating under device
17 | * protected storage.
18 | */
19 | override fun getApplicationContext(): Context = this
20 |
21 | override fun onCreate() = app.onCreate()
22 | override fun onTerminate() = app.onTerminate()
23 | override fun onConfigurationChanged(newConfig: Configuration) = app.onConfigurationChanged(newConfig)
24 | override fun onLowMemory() = app.onLowMemory()
25 | override fun onTrimMemory(level: Int) = app.onTrimMemory(level)
26 | override fun registerComponentCallbacks(callback: ComponentCallbacks?) = app.registerComponentCallbacks(callback)
27 | override fun unregisterComponentCallbacks(callback: ComponentCallbacks?) =
28 | app.unregisterComponentCallbacks(callback)
29 | override fun registerActivityLifecycleCallbacks(callback: ActivityLifecycleCallbacks?) =
30 | app.registerActivityLifecycleCallbacks(callback)
31 | override fun unregisterActivityLifecycleCallbacks(callback: ActivityLifecycleCallbacks?) =
32 | app.unregisterActivityLifecycleCallbacks(callback)
33 | override fun registerOnProvideAssistDataListener(callback: OnProvideAssistDataListener?) =
34 | app.registerOnProvideAssistDataListener(callback)
35 | override fun unregisterOnProvideAssistDataListener(callback: OnProvideAssistDataListener?) =
36 | app.unregisterOnProvideAssistDataListener(callback)
37 | }
38 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/util/Events.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.util
2 |
3 | import java.util.concurrent.ConcurrentHashMap
4 |
5 | /**
6 | * These class are based off https://github.com/1blustone/kotlin-events.
7 | */
8 | open class Event0 : ConcurrentHashMap Unit>() {
9 | operator fun invoke() {
10 | for ((_, handler) in this) handler()
11 | }
12 | }
13 |
14 | class StickyEvent0 : Event0() {
15 | override fun put(key: Any, value: () -> Unit): (() -> Unit)? =
16 | super.put(key, value).also { if (it == null) value() }
17 | }
18 |
19 | open class Event1 : ConcurrentHashMap Unit>() {
20 | operator fun invoke(arg: T) {
21 | for ((_, handler) in this) handler(arg)
22 | }
23 | }
24 |
25 | class StickyEvent1(private val fire: () -> T) : Event1() {
26 | override fun put(key: Any, value: (T) -> Unit): ((T) -> Unit)? =
27 | super.put(key, value).also { if (it == null) value(fire()) }
28 | }
29 |
30 | open class Event2 : ConcurrentHashMap Unit>() {
31 | operator fun invoke(arg1: T1, arg2: T2) {
32 | for ((_, handler) in this) handler(arg1, arg2)
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/util/FileHelper.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.util
2 |
3 | import android.content.Context
4 | import android.text.TextUtils
5 | import java.io.File
6 | import java.io.FileInputStream
7 | import java.io.FileOutputStream
8 |
9 | object FileHelper {
10 | var cachePath : String = ""
11 | var settingPath : String = ""
12 |
13 | fun init(context:Context){
14 | val path = getDiskCacheDir(context)
15 | settingPath = "$path/setting"
16 | cachePath = "$path/cache"
17 | checkFile(settingPath)
18 | checkFile(cachePath)
19 | }
20 | private fun checkFile(path:String){
21 | val file = File(path)
22 | if (!file.exists()) {
23 | file.mkdir()
24 | }
25 | }
26 | fun moveFile(srcFileName: String, destDir: String): Boolean {
27 | return moveFile(srcFileName, destDir, "")
28 | }
29 | fun getDiskCacheDir(context: Context): String {
30 | var cachePath = context.externalCacheDir
31 | if (cachePath == null) {
32 | cachePath = context.cacheDir
33 | }
34 | try {
35 | return cachePath!!.absolutePath
36 | } catch (e: Exception) {
37 | }
38 | return ""
39 | }
40 | /**
41 | * 移动文件
42 | *
43 | * @param srcFileName 源文件完整路径
44 | * @param destDir 目的目录完整路径
45 | * @param name 文件名
46 | * @return 文件移动成功返回true,否则返回false
47 | */
48 | private fun moveFile(srcFileName: String, destDir: String, name: String?): Boolean {
49 | val srcFile = File(srcFileName)
50 | if (!srcFile.exists() || !srcFile.isFile) return false
51 | val destDirFile = File(destDir)
52 | if (!destDirFile.exists()) destDirFile.mkdirs()
53 | return srcFile.renameTo(File(destDir + File.separator + (if (TextUtils.isEmpty(name)) srcFile.name else name)))
54 | }
55 | fun getNameOfUrl(url: String): String {
56 | return getNameOfUrl(url, "")
57 | }
58 |
59 | fun getNameOfUrl(url: String, defaultName: String): String {
60 | var name = ""
61 | val pos = url.lastIndexOf('/')
62 | if (pos >= 0) name = url.substring(pos + 1)
63 |
64 | if (TextUtils.isEmpty(name)) name = defaultName
65 |
66 | return name
67 | }
68 | fun writeFile(fileName: String?, content: String): Boolean {
69 | var res = false
70 | try {
71 | val fout = FileOutputStream(fileName)
72 | fout.write(content.toByteArray(), 0, content.toByteArray().size)
73 | fout.flush()
74 | fout.close()
75 | res = true
76 | } catch (e: java.lang.Exception) {
77 | e.printStackTrace()
78 | }
79 | return res
80 | }
81 | fun readFile(fileName: String?): String {
82 | var res = ""
83 | try {
84 | val fin = FileInputStream(fileName)
85 | // FileInputStream fin = openFileInput(fileName);
86 | // 用这个就不行了,必须用FileInputStream
87 | val length = fin.available()
88 | val buffer = ByteArray(length)
89 | fin.read(buffer)
90 | res = String(buffer, charset("UTF-8"))
91 | fin.close()
92 | } catch (e: java.lang.Exception) {
93 | e.printStackTrace()
94 | }
95 | if (res.startsWith("\ufeff")) {
96 | res = res.substring(1)
97 | }
98 | return res
99 | }
100 |
101 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/util/JLog.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.util
2 |
3 | import com.jtun.router.App
4 | import com.jtun.router.room.AppDatabase
5 | import com.jtun.router.room.Log
6 | import kotlinx.coroutines.launch
7 |
8 | /**
9 | * 保存日志到数据库
10 | */
11 | object JLog {
12 | const val R = 1 //运行日志
13 | const val O = 2 //操作日志
14 | const val T = 3 //第三方应用日志
15 | const val U = 4 //升级日志
16 | const val E = 5 //错误日志
17 |
18 | fun log(type:Int,tag:String,msg:String){
19 | val log = Log(type,tag,msg,System.currentTimeMillis())
20 | App.app.applicationScope.launch {
21 | AppDatabase.instance.logDao.update(log)
22 | }
23 | }
24 | fun r(tag:String,msg:String){
25 | log(R,tag,msg)
26 | }
27 | fun o(tag:String,msg:String){
28 | log(O,tag,msg)
29 | }
30 | fun t(tag:String,msg:String){
31 | log(T,tag,msg)
32 | }
33 | fun u(tag:String,msg:String){
34 | log(U,tag,msg)
35 | }
36 | fun e(tag:String,msg:String){
37 | log(E,tag,msg)
38 | }
39 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/util/KillableTileService.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.util
2 |
3 | import android.annotation.SuppressLint
4 | import android.app.ForegroundServiceStartNotAllowedException
5 | import android.app.PendingIntent
6 | import android.content.ComponentName
7 | import android.content.Intent
8 | import android.content.ServiceConnection
9 | import android.graphics.PixelFormat
10 | import android.os.Build
11 | import android.os.DeadObjectException
12 | import android.os.IBinder
13 | import android.service.quicksettings.Tile
14 | import android.service.quicksettings.TileService
15 | import android.view.View
16 | import android.view.WindowManager
17 | import androidx.core.view.doOnPreDraw
18 | import java.lang.ref.WeakReference
19 |
20 | abstract class KillableTileService : TileService(), ServiceConnection {
21 | protected var tapPending = false
22 |
23 | /**
24 | * Compat helper for setSubtitle.
25 | */
26 | protected fun Tile.subtitle(value: CharSequence?) {
27 | if (Build.VERSION.SDK_INT >= 29) subtitle = value
28 | }
29 |
30 | override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
31 | if (tapPending) {
32 | tapPending = false
33 | onClick()
34 | }
35 | }
36 |
37 | override fun onBind(intent: Intent?) = try {
38 | super.onBind(intent)
39 | } catch (e: RuntimeException) {
40 | if (e.cause !is DeadObjectException) throw e
41 | null
42 | }
43 |
44 | protected fun runActivity(intent: Intent) = unlockAndRun {
45 | if (Build.VERSION.SDK_INT < 34) @Suppress("DEPRECATION") @SuppressLint("StartActivityAndCollapseDeprecated") {
46 | startActivityAndCollapse(intent)
47 | } else startActivityAndCollapse(PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE))
48 | }
49 | fun dismiss() = runActivity(Intent(this, SelfDismissActivity::class.java).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
50 | @Suppress("LeakingThis")
51 | protected val dismissHandle = WeakReference(this)
52 | override fun onDestroy() {
53 | dismissHandle.clear()
54 | super.onDestroy()
55 | }
56 |
57 | // Workaround on U: https://github.com/zhanghai/MaterialFiles/commit/7a2b228dfef8e5080d4cc887208b1ac5458c160e
58 | protected fun startForegroundServiceCompat(service: Intent) {
59 | try {
60 | startForegroundService(service)
61 | } catch (e: ForegroundServiceStartNotAllowedException) {
62 | if (Build.VERSION.SDK_INT != 34) throw e
63 | val windowManager = getSystemService(WindowManager::class.java)
64 | val view = View(this)
65 | windowManager.addView(view, WindowManager.LayoutParams().apply {
66 | type = WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW + 35
67 | format = PixelFormat.TRANSLUCENT
68 | token = UnblockCentral.TileService_mToken.get(this@KillableTileService) as IBinder?
69 | })
70 | view.doOnPreDraw {
71 | view.post {
72 | view.invalidate()
73 | view.doOnPreDraw {
74 | try {
75 | startForegroundService(service)
76 | } finally {
77 | windowManager.removeView(view)
78 | }
79 | }
80 | }
81 | }
82 | }
83 | }
84 | }
85 | typealias TileServiceDismissHandle = WeakReference
86 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/util/RangeInput.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.util
2 |
3 | object RangeInput {
4 | fun toString(input: IntArray) = StringBuilder().apply {
5 | if (input.isEmpty()) return@apply
6 | input.sort()
7 | var pending: Int? = null
8 | var last = input[0]
9 | append(last)
10 | for (channel in input.asSequence().drop(1)) {
11 | if (channel == last + 1) pending = channel else {
12 | pending?.let {
13 | append('-')
14 | append(it)
15 | pending = null
16 | }
17 | append(",\u200b") // zero-width space to save space
18 | append(channel)
19 | }
20 | last = channel
21 | }
22 | pending?.let {
23 | append('-')
24 | append(it)
25 | }
26 | }.toString()
27 | fun toString(input: Set?) = input?.run { toString(toIntArray()) }
28 |
29 | fun fromString(input: CharSequence?, min: Int = 1, max: Int = 999) = mutableSetOf().apply {
30 | if (input == null) return@apply
31 | for (unit in input.split(',')) {
32 | if (unit.isBlank()) continue
33 | val blocks = unit.split('-', limit = 2).map { i ->
34 | i.trim { it == '\u200b' || it.isWhitespace() }.toInt()
35 | }
36 | require(blocks[0] in min..max) { "Out of range: ${blocks[0]}" }
37 | if (blocks.size == 2) {
38 | require(blocks[1] in min..max) { "Out of range: ${blocks[1]}" }
39 | addAll(blocks[0]..blocks[1])
40 | } else add(blocks[0])
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/util/RootSession.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.util
2 |
3 | import android.util.Log
4 | import be.mygod.librootkotlinx.RootServer
5 | import com.jtun.router.root.RootManager
6 | import com.jtun.router.root.RoutingCommands
7 | import kotlinx.coroutines.runBlocking
8 | import timber.log.Timber
9 | import java.util.*
10 | import java.util.concurrent.locks.ReentrantLock
11 | import kotlin.concurrent.withLock
12 |
13 | class RootSession : AutoCloseable {
14 | companion object {
15 | private val monitor = ReentrantLock()
16 |
17 | fun use(operation: (RootSession) -> T) = monitor.withLock { operation(RootSession()) }
18 | fun beginTransaction(): Transaction {
19 | monitor.lock()
20 | try {
21 | return RootSession().Transaction()
22 | } catch (e: Exception) {
23 | monitor.unlock()
24 | throw e
25 | }
26 | }
27 | }
28 |
29 | private var server: RootServer? = runBlocking { RootManager.acquire() }
30 | override fun close() {
31 | server?.let { runBlocking { RootManager.release(it) } }
32 | server = null
33 | }
34 |
35 | /**
36 | * Don't care about the results, but still sync.
37 | */
38 | fun submit(command: String) = execQuiet(command).message(listOf(command))?.let { Timber.v(it) }
39 |
40 | fun execQuiet(command: String, redirect: Boolean = false) = runBlocking {
41 | server!!.execute(RoutingCommands.Process(listOf("sh", "-c", command), redirect))
42 | }
43 | fun exec(command: String) = execQuiet(command).check(listOf(command))
44 |
45 | /**
46 | * This transaction is different from what you may have in mind since you can revert it after committing it.
47 | */
48 | inner class Transaction {
49 | private val revertCommands = LinkedList()
50 |
51 | fun exec(command: String, revert: String? = null) = execQuiet(command, revert).check(listOf(command))
52 | fun execQuiet(command: String, revert: String? = null): RoutingCommands.ProcessResult {
53 | Log.i("RootSession","command: $command ,revert : $revert")
54 | if (revert != null) revertCommands.addFirst(revert) // add first just in case exec fails
55 | return this@RootSession.execQuiet(command)
56 | }
57 |
58 | fun commit() = monitor.unlock()
59 |
60 | fun revert() {
61 | var locked = monitor.isHeldByCurrentThread
62 | try {
63 | if (revertCommands.isEmpty()) return
64 | val shell = if (locked) this@RootSession else {
65 | monitor.lock()
66 | locked = true
67 | RootSession()
68 | }
69 | revertCommands.forEach { shell.submit(it) }
70 | } catch (e: Exception) { // if revert fails, it should fail silently
71 | Timber.d(e)
72 | } finally {
73 | revertCommands.clear()
74 | if (locked) monitor.unlock() // commit
75 | }
76 | }
77 |
78 | fun safeguard(work: Transaction.() -> Unit) = try {
79 | work()
80 | commit()
81 | this
82 | } catch (e: Exception) {
83 | revert()
84 | throw e
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/util/SelfDismissActivity.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.util
2 |
3 | import android.app.Activity
4 | import android.os.Bundle
5 |
6 | class SelfDismissActivity : Activity() {
7 | override fun onCreate(savedInstanceState: Bundle?) {
8 | super.onCreate(savedInstanceState)
9 | finish()
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/util/ServiceForegroundConnector.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.util
2 |
3 | import android.app.Service
4 | import android.content.Context
5 | import android.content.Intent
6 | import android.content.ServiceConnection
7 | import androidx.fragment.app.Fragment
8 | import androidx.lifecycle.DefaultLifecycleObserver
9 | import androidx.lifecycle.LifecycleOwner
10 | import kotlin.reflect.KClass
11 |
12 | /**
13 | * owner also needs to be Context/Fragment.
14 | */
15 | class ServiceForegroundConnector(private val owner: LifecycleOwner, private val connection: ServiceConnection,
16 | private val clazz: KClass) : DefaultLifecycleObserver {
17 | init {
18 | owner.lifecycle.addObserver(this)
19 | }
20 |
21 | private val context get() = when (owner) {
22 | is Context -> owner
23 | is Fragment -> owner.requireContext()
24 | else -> throw UnsupportedOperationException("Unsupported owner")
25 | }
26 |
27 | override fun onStart(owner: LifecycleOwner) {
28 | val context = context
29 | context.bindService(Intent(context, clazz.java), connection, Context.BIND_AUTO_CREATE)
30 | }
31 |
32 | override fun onStop(owner: LifecycleOwner) = context.stopAndUnbind(connection)
33 | }
34 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/util/Services.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.util
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 | import android.net.ConnectivityManager
6 | import android.net.NetworkRequest
7 | import android.net.wifi.WifiManager
8 | import android.net.wifi.p2p.WifiP2pManager
9 | import android.os.Handler
10 | import android.os.Looper
11 | import androidx.core.content.getSystemService
12 | import timber.log.Timber
13 |
14 | object Services {
15 | private lateinit var contextInit: () -> Context
16 | val context by lazy { contextInit() }
17 | fun init(context: () -> Context) {
18 | contextInit = context
19 | }
20 |
21 | val mainHandler by lazy { Handler(Looper.getMainLooper()) }
22 | val connectivity by lazy { context.getSystemService()!! }
23 | val p2p by lazy {
24 | try {
25 | context.getSystemService()
26 | } catch (e: RuntimeException) {
27 | Timber.w(e)
28 | null
29 | }
30 | }
31 | val wifi by lazy { context.getSystemService()!! }
32 |
33 | val netd by lazy @SuppressLint("WrongConstant") { context.getSystemService("netd")!! }
34 |
35 | fun registerNetworkCallback(request: NetworkRequest, networkCallback: ConnectivityManager.NetworkCallback) =
36 | connectivity.registerNetworkCallback(request, networkCallback, mainHandler)
37 | }
38 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/util/TetheringUtil.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.util
2 |
3 | import android.content.Intent
4 | import com.jtun.router.App
5 | import com.jtun.router.TetheringService
6 | import com.jtun.router.control.WifiApControl
7 |
8 | object TetheringUtil {
9 | fun startTetheringService(){
10 | App.app.startForegroundService(
11 | Intent(App.app, TetheringService::class.java)
12 | .putExtra(TetheringService.EXTRA_ADD_INTERFACES, arrayOf(WifiApControl.getInstance().getActiveIFace())))
13 | }
14 | fun stopTetheringService(){
15 | App.app.stopService(
16 | Intent(App.app, TetheringService::class.java)
17 | .putExtra(TetheringService.EXTRA_ADD_INTERFACES, arrayOf(WifiApControl.getInstance().getActiveIFace())))
18 | }
19 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/util/UnblockCentral.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.util
2 |
3 | import android.annotation.SuppressLint
4 | import android.net.MacAddress
5 | import android.net.wifi.SoftApConfiguration
6 | import android.net.wifi.p2p.WifiP2pConfig
7 | import android.service.quicksettings.TileService
8 | import androidx.annotation.RequiresApi
9 | import org.lsposed.hiddenapibypass.HiddenApiBypass
10 |
11 | /**
12 | * The central object for accessing all the useful blocked APIs. Thanks Google!
13 | *
14 | * Lazy cannot be used directly as it will create inner classes.
15 | */
16 | @SuppressLint("BlockedPrivateApi", "DiscouragedPrivateApi", "SoonBlockedPrivateApi")
17 | object UnblockCentral {
18 | var needInit = true
19 | /**
20 | * Retrieve this property before doing dangerous shit.
21 | */
22 | private val init by lazy { if (needInit) check(HiddenApiBypass.setHiddenApiExemptions("")) }
23 |
24 | @RequiresApi(33)
25 | fun getCountryCode(clazz: Class<*>) = init.let { clazz.getDeclaredMethod("getCountryCode") }
26 |
27 | @RequiresApi(33)
28 | fun setRandomizedMacAddress(clazz: Class<*>) = init.let {
29 | clazz.getDeclaredMethod("setRandomizedMacAddress", MacAddress::class.java)
30 | }
31 |
32 | @get:RequiresApi(31)
33 | val SoftApConfiguration_BAND_TYPES get() = init.let {
34 | SoftApConfiguration::class.java.getDeclaredField("BAND_TYPES").get(null) as IntArray
35 | }
36 |
37 | @RequiresApi(31)
38 | fun getApInstanceIdentifier(clazz: Class<*>) = init.let { clazz.getDeclaredMethod("getApInstanceIdentifier") }
39 |
40 | @get:RequiresApi(29)
41 | val WifiP2pConfig_Builder_mNetworkName get() = init.let {
42 | WifiP2pConfig.Builder::class.java.getDeclaredField("mNetworkName").apply { isAccessible = true }
43 | }
44 |
45 | val TileService_mToken get() = init.let {
46 | TileService::class.java.getDeclaredField("mToken").apply { isAccessible = true }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/widget/BaseWebListener.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.widget
2 |
3 | import android.graphics.Bitmap
4 |
5 | abstract class BaseWebListener : AdvancedWebView.Listener {
6 | override fun onPageStarted(url: String?, favicon: Bitmap?) {
7 | }
8 |
9 | override fun onPageFinished(url: String?) {
10 | }
11 |
12 | override fun onPageError(errorCode: Int, description: String?, failingUrl: String?) {
13 | }
14 |
15 | override fun onDownloadRequested(
16 | url: String?,
17 | suggestedFilename: String?,
18 | mimeType: String?,
19 | contentLength: Long,
20 | contentDisposition: String?,
21 | userAgent: String?
22 | ) {
23 | }
24 |
25 | override fun onExternalPageRequest(url: String?) {
26 | }
27 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/jtun/router/widget/SmartSnackbar.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router.widget
2 |
3 |
4 | import android.annotation.SuppressLint
5 | import android.os.Looper
6 | import android.view.View
7 | import android.widget.Toast
8 | import androidx.annotation.StringRes
9 | import androidx.lifecycle.DefaultLifecycleObserver
10 | import androidx.lifecycle.LifecycleOwner
11 | import androidx.lifecycle.findViewTreeLifecycleOwner
12 | import com.jtun.router.App.Companion.app
13 | import com.jtun.router.util.readableMessage
14 | import com.google.android.material.snackbar.Snackbar
15 | import java.util.concurrent.atomic.AtomicReference
16 |
17 | sealed class SmartSnackbar {
18 | companion object {
19 | private val holder = AtomicReference()
20 |
21 | fun make(@StringRes text: Int): SmartSnackbar = make(app.getText(text))
22 | fun make(text: CharSequence = ""): SmartSnackbar {
23 | val holder = holder.get()
24 | return if (holder == null) @SuppressLint("ShowToast") {
25 | if (Looper.myLooper() == null) Looper.prepare()
26 | ToastWrapper(Toast.makeText(app, text, Toast.LENGTH_LONG))
27 | } else SnackbarWrapper(Snackbar.make(holder, text, Snackbar.LENGTH_LONG))
28 | }
29 | fun make(e: Throwable) = make(e.readableMessage)
30 | }
31 |
32 | class Register(private val view: View) : DefaultLifecycleObserver {
33 | init {
34 | view.findViewTreeLifecycleOwner()!!.lifecycle.addObserver(this)
35 | }
36 |
37 | override fun onResume(owner: LifecycleOwner) = holder.set(view)
38 | override fun onPause(owner: LifecycleOwner) {
39 | holder.compareAndSet(view, null)
40 | }
41 | }
42 |
43 | abstract fun show()
44 | open fun action(@StringRes id: Int, listener: (View) -> Unit) { }
45 | open fun shortToast() = this
46 | }
47 |
48 | private class SnackbarWrapper(private val snackbar: Snackbar) : SmartSnackbar() {
49 | override fun show() = snackbar.show()
50 |
51 | override fun action(@StringRes id: Int, listener: (View) -> Unit) {
52 | snackbar.setAction(id, listener)
53 | }
54 | }
55 |
56 | private class ToastWrapper(private val toast: Toast) : SmartSnackbar() {
57 | override fun show() = toast.show()
58 |
59 | override fun shortToast() = apply { toast.duration = Toast.LENGTH_SHORT }
60 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/btn_close.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | -
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | -
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/btn_save.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | -
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | -
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_action_settings_ethernet.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_action_settings_input_antenna.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_content_inbox.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_device_bluetooth.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_device_network_wifi.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_device_usb.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_device_wifi_tethering.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_image_flash_on.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_quick_settings_tile_on.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
13 |
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_connect.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_download.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_logo.xml:
--------------------------------------------------------------------------------
1 |
6 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_reboot.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_upload.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_wifi.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
19 |
26 |
33 |
37 |
38 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/layout_item_index_page.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/layout_main_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_confirm_program.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
23 |
24 |
30 |
31 |
32 |
33 |
42 |
43 |
49 |
50 |
51 |
63 |
64 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtun-coder/JtunRouter/7542269a1239a6dd6027a13dd362520a5c31384a/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtun-coder/JtunRouter/7542269a1239a6dd6027a13dd362520a5c31384a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/icon_close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtun-coder/JtunRouter/7542269a1239a6dd6027a13dd362520a5c31384a/app/src/main/res/mipmap-hdpi/icon_close.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtun-coder/JtunRouter/7542269a1239a6dd6027a13dd362520a5c31384a/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtun-coder/JtunRouter/7542269a1239a6dd6027a13dd362520a5c31384a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtun-coder/JtunRouter/7542269a1239a6dd6027a13dd362520a5c31384a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtun-coder/JtunRouter/7542269a1239a6dd6027a13dd362520a5c31384a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtun-coder/JtunRouter/7542269a1239a6dd6027a13dd362520a5c31384a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtun-coder/JtunRouter/7542269a1239a6dd6027a13dd362520a5c31384a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtun-coder/JtunRouter/7542269a1239a6dd6027a13dd362520a5c31384a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtun-coder/JtunRouter/7542269a1239a6dd6027a13dd362520a5c31384a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 | #F5F5F5
11 | #000000
12 | #4CAF50
13 | #2e7d32
14 | #999999
15 | #EAEBF6
16 | #f5f5f5
17 | #333333
18 | #02a7f0
19 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 14sp
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values/style.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
12 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/network_security_config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/app/src/test/java/com/jtun/router/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.jtun.router
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @OptIn(ExperimentalStdlibApi::class)
14 | @Test
15 | fun addition_isCorrect() {
16 | val ts = 1733797894
17 | System.out.println("ts" + intToByteArray4(ts).toHexString())
18 | }
19 |
20 | private fun intToByteArray4(num: Int): ByteArray {
21 | var byteArray = ByteArray(4)
22 | var highH = ((num shr 24) and 0xff).toByte()
23 | var highL = ((num shr 16) and 0xff).toByte()
24 | var LowH = ((num shr 8) and 0xff).toByte()
25 | var LowL = (num and 0xff).toByte()
26 | byteArray[0] = highH
27 | byteArray[1] = highL
28 | byteArray[2] = LowH
29 | byteArray[3] = LowL
30 | return byteArray
31 | }
32 |
33 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | buildscript {
3 | ext.kotlin_version = "1.9.0"
4 | repositories {
5 | mavenCentral()
6 | google()
7 | jcenter()
8 | }
9 | dependencies {
10 | classpath 'com.android.tools.build:gradle:8.2.0' // Update this line
11 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
12 | // NOTE: Do not place your application dependencies here; they belong
13 | // in the individual module build.gradle files
14 | }
15 | }
16 |
17 | allprojects {
18 | repositories {
19 | mavenCentral()
20 | google()
21 | jcenter()
22 | maven { url 'https://jitpack.io' }
23 | }
24 | }
25 |
26 | task clean(type: Delete) {
27 | delete rootProject.buildDir
28 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. For more details, visit
12 | # https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jtun-coder/JtunRouter/7542269a1239a6dd6027a13dd362520a5c31384a/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = "RoutingService"
2 | include ':app'
3 | include (":localsocket")
4 | project(":localsocket").projectDir = new File("C:/Users/qlx/AndroidStudioProjects/LocalSocket")
--------------------------------------------------------------------------------