Unverified Commit 685f8408 authored by Mygod's avatar Mygod Committed by GitHub

Merge pull request #2161 from shadowsocks/q-beta

Enhancements for Android Q beta
parents 2172abfe 0f0e0b26
...@@ -7,12 +7,11 @@ buildscript { ...@@ -7,12 +7,11 @@ buildscript {
javaVersion = JavaVersion.VERSION_1_8 javaVersion = JavaVersion.VERSION_1_8
kotlinVersion = '1.3.31' kotlinVersion = '1.3.31'
minSdkVersion = 21 minSdkVersion = 21
sdkVersion = 28 sdkVersion = 29
compileSdkVersion = 28 compileSdkVersion = 29
buildToolsVersion = '28.0.3'
junitVersion = '4.12' junitVersion = '4.12'
androidTestVersion = '1.1.1' androidTestVersion = '1.2.0'
androidEspressoVersion = '3.1.1' androidEspressoVersion = '3.2.0'
versionCode = 4080050 versionCode = 4080050
versionName = '4.8.0-nightly' versionName = '4.8.0-nightly'
resConfigs = ['es', 'fa', 'fr', 'ja', 'ko', 'ru', 'tr', 'zh-rCN', 'zh-rTW'] resConfigs = ['es', 'fa', 'fr', 'ja', 'ko', 'ru', 'tr', 'zh-rCN', 'zh-rTW']
...@@ -27,7 +26,7 @@ buildscript { ...@@ -27,7 +26,7 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.4.1' classpath 'com.android.tools.build:gradle:3.5.0-beta04'
classpath 'com.github.ben-manes:gradle-versions-plugin:0.21.0' classpath 'com.github.ben-manes:gradle-versions-plugin:0.21.0'
classpath 'com.google.gms:google-services:4.2.0' classpath 'com.google.gms:google-services:4.2.0'
classpath 'com.vanniktech:gradle-maven-publish-plugin:0.8.0' classpath 'com.vanniktech:gradle-maven-publish-plugin:0.8.0'
......
...@@ -4,7 +4,6 @@ apply plugin: 'kotlin-android-extensions' ...@@ -4,7 +4,6 @@ apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-kapt'
android { android {
buildToolsVersion rootProject.buildToolsVersion
compileSdkVersion rootProject.compileSdkVersion compileSdkVersion rootProject.compileSdkVersion
defaultConfig { defaultConfig {
...@@ -48,14 +47,14 @@ androidExtensions { ...@@ -48,14 +47,14 @@ androidExtensions {
experimental = true experimental = true
} }
def lifecycleVersion = '2.0.0' def lifecycleVersion = '2.1.0-beta01'
def roomVersion = '2.0.0' def roomVersion = '2.1.0-rc01'
dependencies { dependencies {
api project(':plugin') api project(':plugin')
api "androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion" api "androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion"
api "androidx.lifecycle:lifecycle-extensions:$lifecycleVersion" api "androidx.lifecycle:lifecycle-extensions:$lifecycleVersion"
api "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion" api "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion"
api 'androidx.preference:preference:1.1.0-alpha05' api 'androidx.preference:preference:1.1.0-beta01'
api "androidx.room:room-runtime:$roomVersion" api "androidx.room:room-runtime:$roomVersion"
api 'androidx.work:work-runtime-ktx:2.0.1' api 'androidx.work:work-runtime-ktx:2.0.1'
api 'com.crashlytics.sdk.android:crashlytics:2.10.1' api 'com.crashlytics.sdk.android:crashlytics:2.10.1'
...@@ -69,5 +68,5 @@ dependencies { ...@@ -69,5 +68,5 @@ dependencies {
androidTestImplementation "androidx.room:room-testing:$roomVersion" androidTestImplementation "androidx.room:room-testing:$roomVersion"
androidTestImplementation "androidx.test:runner:$androidTestVersion" androidTestImplementation "androidx.test:runner:$androidTestVersion"
androidTestImplementation "androidx.test.espresso:espresso-core:$androidEspressoVersion" androidTestImplementation "androidx.test.espresso:espresso-core:$androidEspressoVersion"
androidTestImplementation 'androidx.test.ext:junit-ktx:1.1.0' androidTestImplementation 'androidx.test.ext:junit-ktx:1.1.1-beta01'
} }
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:allowBackup="true" android:allowBackup="true"
android:backupAgent="com.github.shadowsocks.ConfigBackupHelper" android:backupAgent="com.github.shadowsocks.ConfigBackupHelper"
android:extractNativeLibs="true"
android:fullBackupContent="@xml/backup_descriptor" android:fullBackupContent="@xml/backup_descriptor"
android:fullBackupOnly="true" android:fullBackupOnly="true"
android:label="@string/app_name" android:label="@string/app_name"
......
...@@ -31,6 +31,7 @@ import android.content.Intent ...@@ -31,6 +31,7 @@ import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.content.pm.PackageInfo import android.content.pm.PackageInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.net.ConnectivityManager
import android.os.Build import android.os.Build
import android.os.UserManager import android.os.UserManager
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
...@@ -61,6 +62,7 @@ object Core { ...@@ -61,6 +62,7 @@ object Core {
lateinit var app: Application lateinit var app: Application
lateinit var configureIntent: (Context) -> PendingIntent lateinit var configureIntent: (Context) -> PendingIntent
val connectivity by lazy { app.getSystemService<ConnectivityManager>()!! }
val packageInfo: PackageInfo by lazy { getPackageInfo(app.packageName) } val packageInfo: PackageInfo by lazy { getPackageInfo(app.packageName) }
val deviceStorage by lazy { if (Build.VERSION.SDK_INT < 24) app else DeviceStorageApp(app) } val deviceStorage by lazy { if (Build.VERSION.SDK_INT < 24) app else DeviceStorageApp(app) }
val analytics: FirebaseAnalytics by lazy { FirebaseAnalytics.getInstance(deviceStorage) } val analytics: FirebaseAnalytics by lazy { FirebaseAnalytics.getInstance(deviceStorage) }
......
...@@ -46,7 +46,6 @@ import com.google.firebase.analytics.FirebaseAnalytics ...@@ -46,7 +46,6 @@ import com.google.firebase.analytics.FirebaseAnalytics
import kotlinx.coroutines.* import kotlinx.coroutines.*
import java.io.File import java.io.File
import java.net.BindException import java.net.BindException
import java.net.InetAddress
import java.net.URL import java.net.URL
import java.net.UnknownHostException import java.net.UnknownHostException
import java.util.* import java.util.*
...@@ -298,7 +297,7 @@ object BaseService { ...@@ -298,7 +297,7 @@ object BaseService {
listOfNotNull(data.proxy, data.udpFallback).forEach { it.trafficMonitor?.persistStats(it.profile.id) } listOfNotNull(data.proxy, data.udpFallback).forEach { it.trafficMonitor?.persistStats(it.profile.id) }
suspend fun preInit() { } suspend fun preInit() { }
suspend fun resolver(host: String) = InetAddress.getAllByName(host) suspend fun resolver(host: String) = DnsResolverCompat.resolveOnActiveNetwork(host)
suspend fun openConnection(url: URL) = url.openConnection() suspend fun openConnection(url: URL) = url.openConnection()
fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
......
/*******************************************************************************
* *
* Copyright (C) 2019 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2019 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
package com.github.shadowsocks.bg
import android.annotation.TargetApi
import android.net.DnsResolver
import android.net.Network
import android.os.Build
import android.os.CancellationSignal
import com.github.shadowsocks.Core
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.suspendCancellableCoroutine
import java.net.InetAddress
import java.util.concurrent.Executor
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
sealed class DnsResolverCompat {
companion object : DnsResolverCompat() {
private val instance by lazy { if (Build.VERSION.SDK_INT >= 29) DnsResolverCompat29 else DnsResolverCompat21 }
override suspend fun resolve(network: Network, host: String) = instance.resolve(network, host)
override suspend fun resolveOnActiveNetwork(host: String) = instance.resolveOnActiveNetwork(host)
}
abstract suspend fun resolve(network: Network, host: String): Array<InetAddress>
abstract suspend fun resolveOnActiveNetwork(host: String): Array<InetAddress>
private object DnsResolverCompat21 : DnsResolverCompat() {
override suspend fun resolve(network: Network, host: String) =
GlobalScope.async(Dispatchers.IO) { network.getAllByName(host) }.await()
override suspend fun resolveOnActiveNetwork(host: String) =
GlobalScope.async(Dispatchers.IO) { InetAddress.getAllByName(host) }.await()
}
@TargetApi(29)
private object DnsResolverCompat29 : DnsResolverCompat() {
/**
* This executor will run on its caller directly. On Q beta 3, this results in calling in main thread.
*/
private object InPlaceExecutor : Executor {
override fun execute(command: Runnable?) = command!!.run()
}
override suspend fun resolve(network: Network, host: String): Array<InetAddress> {
return suspendCancellableCoroutine { cont ->
val signal = CancellationSignal()
cont.invokeOnCancellation { signal.cancel() }
// retry should be handled by client instead
DnsResolver.getInstance().query(network, host, DnsResolver.FLAG_NO_RETRY, InPlaceExecutor,
signal, object : DnsResolver.Callback<Collection<InetAddress>> {
override fun onAnswer(answer: Collection<InetAddress>, rcode: Int) =
cont.resume(answer.toTypedArray())
override fun onError(error: DnsResolver.DnsException) = cont.resumeWithException(error)
})
}
}
override suspend fun resolveOnActiveNetwork(host: String): Array<InetAddress> {
return resolve(Core.connectivity.activeNetwork ?: return emptyArray(), host)
}
}
}
...@@ -37,7 +37,7 @@ object Executable { ...@@ -37,7 +37,7 @@ object Executable {
private val EXECUTABLES = setOf(SS_LOCAL, REDSOCKS, TUN2SOCKS) private val EXECUTABLES = setOf(SS_LOCAL, REDSOCKS, TUN2SOCKS)
fun killAll() { fun killAll() {
for (process in File("/proc").listFiles { _, name -> TextUtils.isDigitsOnly(name) }) { for (process in File("/proc").listFiles { _, name -> TextUtils.isDigitsOnly(name) } ?: return) {
val exe = File(try { val exe = File(try {
File(process, "cmdline").inputStream().bufferedReader().readText() File(process, "cmdline").inputStream().bufferedReader().readText()
} catch (_: IOException) { } catch (_: IOException) {
......
...@@ -84,8 +84,7 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro ...@@ -84,8 +84,7 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro
if (profile.host.parseNumericAddress() == null) { if (profile.host.parseNumericAddress() == null) {
var retries = 0 var retries = 0
while (true) try { while (true) try {
val io = GlobalScope.async(Dispatchers.IO) { service.resolver(profile.host) } profile.host = service.resolver(profile.host).firstOrNull()?.hostAddress ?: throw UnknownHostException()
profile.host = io.await().firstOrNull()?.hostAddress ?: throw UnknownHostException()
return return
} catch (e: UnknownHostException) { } catch (e: UnknownHostException) {
// retries are only needed on Chrome OS where arc0 is brought up/down during VPN changes // retries are only needed on Chrome OS where arc0 is brought up/down during VPN changes
......
...@@ -108,8 +108,8 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface { ...@@ -108,8 +108,8 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
if (active && Build.VERSION.SDK_INT >= 22) setUnderlyingNetworks(underlyingNetworks) if (active && Build.VERSION.SDK_INT >= 22) setUnderlyingNetworks(underlyingNetworks)
} }
private val underlyingNetworks get() = private val underlyingNetworks get() =
// clearing underlyingNetworks makes Android 9+ consider the network to be metered // clearing underlyingNetworks makes Android 9 consider the network to be metered
if (Build.VERSION.SDK_INT >= 28 && metered) null else underlyingNetwork?.let { arrayOf(it) } if (Build.VERSION.SDK_INT == 28 && metered) null else underlyingNetwork?.let { arrayOf(it) }
override fun onBind(intent: Intent) = when (intent.action) { override fun onBind(intent: Intent) = when (intent.action) {
SERVICE_INTERFACE -> super<BaseVpnService>.onBind(intent) SERVICE_INTERFACE -> super<BaseVpnService>.onBind(intent)
...@@ -139,7 +139,7 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface { ...@@ -139,7 +139,7 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
} }
override suspend fun preInit() = DefaultNetworkListener.start(this) { underlyingNetwork = it } override suspend fun preInit() = DefaultNetworkListener.start(this) { underlyingNetwork = it }
override suspend fun resolver(host: String) = DefaultNetworkListener.get().getAllByName(host) override suspend fun resolver(host: String) = DnsResolverCompat.resolve(DefaultNetworkListener.get(), host)
override suspend fun openConnection(url: URL) = DefaultNetworkListener.get().openConnection(url) override suspend fun openConnection(url: URL) = DefaultNetworkListener.get().openConnection(url)
override suspend fun startProcesses() { override suspend fun startProcesses() {
...@@ -195,7 +195,10 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface { ...@@ -195,7 +195,10 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
metered = profile.metered metered = profile.metered
active = true // possible race condition here? active = true // possible race condition here?
if (Build.VERSION.SDK_INT >= 22) builder.setUnderlyingNetworks(underlyingNetworks) if (Build.VERSION.SDK_INT >= 22) {
builder.setUnderlyingNetworks(underlyingNetworks)
if (Build.VERSION.SDK_INT >= 29) builder.setMetered(metered)
}
val conn = builder.establish() ?: throw NullConnectionException() val conn = builder.establish() ?: throw NullConnectionException()
this.conn = conn this.conn = conn
......
...@@ -39,6 +39,7 @@ abstract class PrivateDatabase : RoomDatabase() { ...@@ -39,6 +39,7 @@ abstract class PrivateDatabase : RoomDatabase() {
Migration27, Migration27,
Migration28 Migration28
) )
.enableMultiInstanceInvalidation()
.fallbackToDestructiveMigration() .fallbackToDestructiveMigration()
.allowMainThreadQueries() .allowMainThreadQueries()
.build() .build()
......
...@@ -36,6 +36,7 @@ abstract class PublicDatabase : RoomDatabase() { ...@@ -36,6 +36,7 @@ abstract class PublicDatabase : RoomDatabase() {
.addMigrations( .addMigrations(
Migration3 Migration3
) )
.enableMultiInstanceInvalidation()
.fallbackToDestructiveMigration() .fallbackToDestructiveMigration()
.build() .build()
} }
......
...@@ -26,9 +26,8 @@ import android.net.Network ...@@ -26,9 +26,8 @@ import android.net.Network
import android.net.NetworkCapabilities import android.net.NetworkCapabilities
import android.net.NetworkRequest import android.net.NetworkRequest
import android.os.Build import android.os.Build
import androidx.core.content.getSystemService
import com.crashlytics.android.Crashlytics import com.crashlytics.android.Crashlytics
import com.github.shadowsocks.Core.app import com.github.shadowsocks.Core
import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
...@@ -84,7 +83,7 @@ object DefaultNetworkListener { ...@@ -84,7 +83,7 @@ object DefaultNetworkListener {
suspend fun start(key: Any, listener: (Network?) -> Unit) = networkActor.send(NetworkMessage.Start(key, listener)) suspend fun start(key: Any, listener: (Network?) -> Unit) = networkActor.send(NetworkMessage.Start(key, listener))
suspend fun get() = if (fallback) @TargetApi(23) { suspend fun get() = if (fallback) @TargetApi(23) {
connectivity.activeNetwork ?: throw UnknownHostException() // failed to listen, return current if available Core.connectivity.activeNetwork ?: throw UnknownHostException() // failed to listen, return current if available
} else NetworkMessage.Get().run { } else NetworkMessage.Get().run {
networkActor.send(this) networkActor.send(this)
response.await() response.await()
...@@ -102,7 +101,6 @@ object DefaultNetworkListener { ...@@ -102,7 +101,6 @@ object DefaultNetworkListener {
} }
private var fallback = false private var fallback = false
private val connectivity = app.getSystemService<ConnectivityManager>()!!
private val request = NetworkRequest.Builder().apply { private val request = NetworkRequest.Builder().apply {
addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
...@@ -119,16 +117,16 @@ object DefaultNetworkListener { ...@@ -119,16 +117,16 @@ object DefaultNetworkListener {
*/ */
private fun register() { private fun register() {
if (Build.VERSION.SDK_INT in 24..27) @TargetApi(24) { if (Build.VERSION.SDK_INT in 24..27) @TargetApi(24) {
connectivity.registerDefaultNetworkCallback(Callback) Core.connectivity.registerDefaultNetworkCallback(Callback)
} else try { } else try {
fallback = false fallback = false
// we want REQUEST here instead of LISTEN // we want REQUEST here instead of LISTEN
connectivity.requestNetwork(request, Callback) Core.connectivity.requestNetwork(request, Callback)
} catch (e: SecurityException) { } catch (e: SecurityException) {
// known bug: https://stackoverflow.com/a/33509180/2245107 // known bug: https://stackoverflow.com/a/33509180/2245107
if (Build.VERSION.SDK_INT != 23) Crashlytics.logException(e) if (Build.VERSION.SDK_INT != 23) Crashlytics.logException(e)
fallback = true fallback = true
} }
} }
private fun unregister() = connectivity.unregisterNetworkCallback(Callback) private fun unregister() = Core.connectivity.unregisterNetworkCallback(Callback)
} }
...@@ -126,7 +126,7 @@ class LocalDnsServer(private val localResolver: suspend (String) -> Array<InetAd ...@@ -126,7 +126,7 @@ class LocalDnsServer(private val localResolver: suspend (String) -> Array<InetAd
if (forwardOnly) return@supervisorScope remote.await() if (forwardOnly) return@supervisorScope remote.await()
if (remoteDomainMatcher?.containsMatchIn(host) == true) return@supervisorScope remote.await() if (remoteDomainMatcher?.containsMatchIn(host) == true) return@supervisorScope remote.await()
val localResults = try { val localResults = try {
withTimeout(TIMEOUT) { GlobalScope.async(Dispatchers.IO) { localResolver(host) }.await() } withTimeout(TIMEOUT) { localResolver(host) }
} catch (_: TimeoutCancellationException) { } catch (_: TimeoutCancellationException) {
Crashlytics.log(Log.WARN, TAG, "Local resolving timed out, falling back to remote resolving") Crashlytics.log(Log.WARN, TAG, "Local resolving timed out, falling back to remote resolving")
return@supervisorScope remote.await() return@supervisorScope remote.await()
......
...@@ -30,10 +30,10 @@ import com.github.shadowsocks.utils.signaturesCompat ...@@ -30,10 +30,10 @@ import com.github.shadowsocks.utils.signaturesCompat
abstract class ResolvedPlugin(protected val resolveInfo: ResolveInfo) : Plugin() { abstract class ResolvedPlugin(protected val resolveInfo: ResolveInfo) : Plugin() {
protected abstract val metaData: Bundle protected abstract val metaData: Bundle
override val id: String by lazy { metaData.getString(PluginContract.METADATA_KEY_ID) } override val id: String by lazy { metaData.getString(PluginContract.METADATA_KEY_ID)!! }
override val label: CharSequence by lazy { resolveInfo.loadLabel(app.packageManager) } override val label: CharSequence by lazy { resolveInfo.loadLabel(app.packageManager) }
override val icon: Drawable by lazy { resolveInfo.loadIcon(app.packageManager) } override val icon: Drawable by lazy { resolveInfo.loadIcon(app.packageManager) }
override val defaultConfig: String by lazy { metaData.getString(PluginContract.METADATA_KEY_DEFAULT_CONFIG) } override val defaultConfig: String? by lazy { metaData.getString(PluginContract.METADATA_KEY_DEFAULT_CONFIG) }
override val packageName: String get() = resolveInfo.resolvePackageName override val packageName: String get() = resolveInfo.resolvePackageName
override val trusted by lazy { override val trusted by lazy {
Core.getPackageInfo(packageName).signaturesCompat.any(PluginManager.trustedSignatures::contains) Core.getPackageInfo(packageName).signaturesCompat.any(PluginManager.trustedSignatures::contains)
......
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
package com.github.shadowsocks.utils package com.github.shadowsocks.utils
import android.annotation.SuppressLint
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.ContentResolver import android.content.ContentResolver
import android.content.Context import android.content.Context
...@@ -36,25 +37,30 @@ import android.util.TypedValue ...@@ -36,25 +37,30 @@ import android.util.TypedValue
import androidx.annotation.AttrRes import androidx.annotation.AttrRes
import androidx.preference.Preference import androidx.preference.Preference
import com.crashlytics.android.Crashlytics import com.crashlytics.android.Crashlytics
import kotlinx.coroutines.* import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
import java.net.HttpURLConnection import java.net.HttpURLConnection
import java.net.InetAddress import java.net.InetAddress
import kotlin.coroutines.resume import kotlin.coroutines.resume
val Throwable.readableMessage get() = localizedMessage ?: javaClass.name val Throwable.readableMessage get() = localizedMessage ?: javaClass.name
private val parseNumericAddress by lazy { private val parseNumericAddress by lazy @SuppressLint("DiscouragedPrivateApi") {
InetAddress::class.java.getDeclaredMethod("parseNumericAddress", String::class.java).apply { InetAddress::class.java.getDeclaredMethod("parseNumericAddress", String::class.java).apply {
isAccessible = true isAccessible = true
} }
} }
/** /**
* A slightly more performant variant of InetAddress.parseNumericAddress. * A slightly more performant variant of parseNumericAddress.
* *
* Bug: https://issuetracker.google.com/issues/123456213 * Bug in Android 9.0 and lower: https://issuetracker.google.com/issues/123456213
*/ */
fun String.parseNumericAddress(): InetAddress? = Os.inet_pton(OsConstants.AF_INET, this) fun String?.parseNumericAddress(): InetAddress? = Os.inet_pton(OsConstants.AF_INET, this)
?: Os.inet_pton(OsConstants.AF_INET6, this)?.let { parseNumericAddress.invoke(null, this) as InetAddress } ?: Os.inet_pton(OsConstants.AF_INET6, this)?.let {
if (Build.VERSION.SDK_INT >= 29) it else parseNumericAddress.invoke(null, this) as InetAddress
}
fun <K, V> MutableMap<K, V>.computeIfAbsentCompat(key: K, value: () -> V) = if (Build.VERSION.SDK_INT >= 24) fun <K, V> MutableMap<K, V>.computeIfAbsentCompat(key: K, value: () -> V) = if (Build.VERSION.SDK_INT >= 24)
computeIfAbsent(key) { value() } else this[key] ?: value().also { put(key, it) } computeIfAbsent(key) { value() } else this[key] ?: value().also { put(key, it) }
......
...@@ -10,7 +10,6 @@ ...@@ -10,7 +10,6 @@
# Specifies the JVM arguments used for the daemon process. # Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings. # The setting is particularly useful for tweaking memory settings.
android.enableJetifier=true android.enableJetifier=true
android.enableR8=true
android.enableR8.fullMode=true android.enableR8.fullMode=true
android.useAndroidX=true android.useAndroidX=true
......
...@@ -17,7 +17,6 @@ def getCurrentFlavor() { ...@@ -17,7 +17,6 @@ def getCurrentFlavor() {
} }
android { android {
buildToolsVersion rootProject.buildToolsVersion
compileSdkVersion rootProject.compileSdkVersion compileSdkVersion rootProject.compileSdkVersion
defaultConfig { defaultConfig {
applicationId "com.github.shadowsocks" applicationId "com.github.shadowsocks"
...@@ -61,7 +60,7 @@ androidExtensions { ...@@ -61,7 +60,7 @@ androidExtensions {
dependencies { dependencies {
implementation project(':core') implementation project(':core')
implementation 'androidx.browser:browser:1.0.0' implementation 'androidx.browser:browser:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta1'
implementation 'com.google.android.gms:play-services-vision:17.0.2' implementation 'com.google.android.gms:play-services-vision:17.0.2'
implementation 'com.google.firebase:firebase-ads:17.2.1' implementation 'com.google.firebase:firebase-ads:17.2.1'
implementation 'com.takisoft.preferencex:preferencex-simplemenu:1.0.0' implementation 'com.takisoft.preferencex:preferencex-simplemenu:1.0.0'
......
...@@ -266,8 +266,8 @@ class AppManager : AppCompatActivity() { ...@@ -266,8 +266,8 @@ class AppManager : AppCompatActivity() {
return true return true
} }
R.id.action_export_clipboard -> { R.id.action_export_clipboard -> {
clipboard.primaryClip = ClipData.newPlainText(Key.individual, clipboard.setPrimaryClip(ClipData.newPlainText(Key.individual,
"${DataStore.bypass}\n${DataStore.individual}") "${DataStore.bypass}\n${DataStore.individual}"))
Snackbar.make(list, R.string.action_export_msg, Snackbar.LENGTH_LONG).show() Snackbar.make(list, R.string.action_export_msg, Snackbar.LENGTH_LONG).show()
return true return true
} }
......
...@@ -78,15 +78,15 @@ class GlobalSettingsPreferenceFragment : PreferenceFragmentCompat() { ...@@ -78,15 +78,15 @@ class GlobalSettingsPreferenceFragment : PreferenceFragmentCompat() {
tfo.summary = getString(R.string.tcp_fastopen_summary_unsupported, System.getProperty("os.version")) tfo.summary = getString(R.string.tcp_fastopen_summary_unsupported, System.getProperty("os.version"))
} }
hosts.onBindEditTextListener = EditTextPreferenceModifiers.Monospace hosts.setOnBindEditTextListener(EditTextPreferenceModifiers.Monospace)
hosts.summaryProvider = HostsSummaryProvider hosts.summaryProvider = HostsSummaryProvider
val serviceMode = findPreference<Preference>(Key.serviceMode)!! val serviceMode = findPreference<Preference>(Key.serviceMode)!!
val portProxy = findPreference<EditTextPreference>(Key.portProxy)!! val portProxy = findPreference<EditTextPreference>(Key.portProxy)!!
portProxy.onBindEditTextListener = EditTextPreferenceModifiers.Port portProxy.setOnBindEditTextListener(EditTextPreferenceModifiers.Port)
val portLocalDns = findPreference<EditTextPreference>(Key.portLocalDns)!! val portLocalDns = findPreference<EditTextPreference>(Key.portLocalDns)!!
portLocalDns.onBindEditTextListener = EditTextPreferenceModifiers.Port portLocalDns.setOnBindEditTextListener(EditTextPreferenceModifiers.Port)
val portTransproxy = findPreference<EditTextPreference>(Key.portTransproxy)!! val portTransproxy = findPreference<EditTextPreference>(Key.portTransproxy)!!
portTransproxy.onBindEditTextListener = EditTextPreferenceModifiers.Port portTransproxy.setOnBindEditTextListener(EditTextPreferenceModifiers.Port)
val onServiceModeChange = Preference.OnPreferenceChangeListener { _, newValue -> val onServiceModeChange = Preference.OnPreferenceChangeListener { _, newValue ->
val (enabledLocalDns, enabledTransproxy) = when (newValue as String?) { val (enabledLocalDns, enabledTransproxy) = when (newValue as String?) {
Key.modeProxy -> Pair(false, false) Key.modeProxy -> Pair(false, false)
......
...@@ -74,7 +74,7 @@ class ProfileConfigFragment : PreferenceFragmentCompat(), ...@@ -74,7 +74,7 @@ class ProfileConfigFragment : PreferenceFragmentCompat(),
val activity = requireActivity() val activity = requireActivity()
profileId = activity.intent.getLongExtra(Action.EXTRA_PROFILE_ID, -1L) profileId = activity.intent.getLongExtra(Action.EXTRA_PROFILE_ID, -1L)
addPreferencesFromResource(R.xml.pref_profile) addPreferencesFromResource(R.xml.pref_profile)
findPreference<EditTextPreference>(Key.remotePort)!!.onBindEditTextListener = EditTextPreferenceModifiers.Port findPreference<EditTextPreference>(Key.remotePort)!!.setOnBindEditTextListener(EditTextPreferenceModifiers.Port)
findPreference<EditTextPreference>(Key.password)!!.summaryProvider = PasswordSummaryProvider findPreference<EditTextPreference>(Key.password)!!.summaryProvider = PasswordSummaryProvider
val serviceMode = DataStore.serviceMode val serviceMode = DataStore.serviceMode
findPreference<Preference>(Key.remoteDns)!!.isEnabled = serviceMode != Key.modeProxy findPreference<Preference>(Key.remoteDns)!!.isEnabled = serviceMode != Key.modeProxy
...@@ -104,7 +104,7 @@ class ProfileConfigFragment : PreferenceFragmentCompat(), ...@@ -104,7 +104,7 @@ class ProfileConfigFragment : PreferenceFragmentCompat(),
} }
true true
} }
pluginConfigure.onBindEditTextListener = EditTextPreferenceModifiers.Monospace pluginConfigure.setOnBindEditTextListener(EditTextPreferenceModifiers.Monospace)
pluginConfigure.onPreferenceChangeListener = this pluginConfigure.onPreferenceChangeListener = this
initPlugins() initPlugins()
receiver = Core.listenForPackageChanges(false) { initPlugins() } receiver = Core.listenForPackageChanges(false) { initPlugins() }
......
...@@ -185,7 +185,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -185,7 +185,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
true true
} }
R.id.action_export_clipboard -> { R.id.action_export_clipboard -> {
clipboard.primaryClip = ClipData.newPlainText(null, this.item.toString()) clipboard.setPrimaryClip(ClipData.newPlainText(null, this.item.toString()))
true true
} }
else -> false else -> false
...@@ -381,7 +381,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -381,7 +381,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
R.id.action_export_clipboard -> { R.id.action_export_clipboard -> {
val profiles = ProfileManager.getAllProfiles() val profiles = ProfileManager.getAllProfiles()
(activity as MainActivity).snackbar().setText(if (profiles != null) { (activity as MainActivity).snackbar().setText(if (profiles != null) {
clipboard.primaryClip = ClipData.newPlainText(null, profiles.joinToString("\n")) clipboard.setPrimaryClip(ClipData.newPlainText(null, profiles.joinToString("\n")))
R.string.action_export_msg R.string.action_export_msg
} else R.string.action_export_err).show() } else R.string.action_export_err).show()
true true
...@@ -413,7 +413,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -413,7 +413,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
try { try {
ProfileManager.createProfilesFromJson(data!!.datas.asSequence().map { ProfileManager.createProfilesFromJson(data!!.datas.asSequence().map {
activity.contentResolver.openInputStream(it) activity.contentResolver.openInputStream(it)
}) }.filterNotNull())
} catch (e: Exception) { } catch (e: Exception) {
activity.snackbar(e.readableMessage).show() activity.snackbar(e.readableMessage).show()
} }
...@@ -423,7 +423,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -423,7 +423,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
try { try {
ProfileManager.createProfilesFromJson(data!!.datas.asSequence().map { ProfileManager.createProfilesFromJson(data!!.datas.asSequence().map {
activity.contentResolver.openInputStream(it) activity.contentResolver.openInputStream(it)
}, true) }.filterNotNull(), true)
} catch (e: Exception) { } catch (e: Exception) {
activity.snackbar(e.readableMessage).show() activity.snackbar(e.readableMessage).show()
} }
......
...@@ -427,7 +427,7 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener, ...@@ -427,7 +427,7 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener,
is URL -> acl.urls.add(it) is URL -> acl.urls.add(it)
} }
} }
clipboard.primaryClip = ClipData.newPlainText(null, acl.toString()) clipboard.setPrimaryClip(ClipData.newPlainText(null, acl.toString()))
} }
override fun onMenuItemClick(item: MenuItem): Boolean = when (item.itemId) { override fun onMenuItemClick(item: MenuItem): Boolean = when (item.itemId) {
......
...@@ -99,7 +99,7 @@ class BottomSheetPreferenceDialogFragment : PreferenceDialogFragmentCompat() { ...@@ -99,7 +99,7 @@ class BottomSheetPreferenceDialogFragment : PreferenceDialogFragmentCompat() {
} }
fun setArg(key: String) { fun setArg(key: String) {
arguments = bundleOf(PreferenceDialogFragmentCompat.ARG_KEY to key) arguments = bundleOf(ARG_KEY to key)
} }
private val preference by lazy { getPreference() as IconListPreference } private val preference by lazy { getPreference() as IconListPreference }
......
...@@ -45,17 +45,20 @@ class StatsBar @JvmOverloads constructor(context: Context, attrs: AttributeSet? ...@@ -45,17 +45,20 @@ class StatsBar @JvmOverloads constructor(context: Context, attrs: AttributeSet?
private lateinit var txRateText: TextView private lateinit var txRateText: TextView
private lateinit var rxRateText: TextView private lateinit var rxRateText: TextView
private val tester = ViewModelProviders.of(context as FragmentActivity).get<HttpsTest>() private val tester = ViewModelProviders.of(context as FragmentActivity).get<HttpsTest>()
private val behavior = object : Behavior() { private lateinit var behavior: Behavior
override fun getBehavior(): Behavior {
if (!this::behavior.isInitialized) behavior = object : Behavior() {
val threshold = context.resources.getDimensionPixelSize(R.dimen.stats_bar_scroll_threshold) val threshold = context.resources.getDimensionPixelSize(R.dimen.stats_bar_scroll_threshold)
override fun onNestedScroll(coordinatorLayout: CoordinatorLayout, child: BottomAppBar, target: View, override fun onNestedScroll(coordinatorLayout: CoordinatorLayout, child: BottomAppBar, target: View,
dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int, dyUnconsumed: Int, type: Int, dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int, dyUnconsumed: Int,
consumed: IntArray) { type: Int, consumed: IntArray) {
val dy = dyConsumed + dyUnconsumed val dy = dyConsumed + dyUnconsumed
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, if (Math.abs(dy) >= threshold) dy else 0, super.onNestedScroll(coordinatorLayout, child, target, dxConsumed,
dxUnconsumed, 0, type, consumed) if (Math.abs(dy) >= threshold) dy else 0, dxUnconsumed, 0, type, consumed)
} }
} }
override fun getBehavior() = behavior return behavior
}
override fun setOnClickListener(l: OnClickListener?) { override fun setOnClickListener(l: OnClickListener?) {
statusText = findViewById(R.id.status) statusText = findViewById(R.id.status)
......
...@@ -14,7 +14,6 @@ def getRepositoryPassword() { ...@@ -14,7 +14,6 @@ def getRepositoryPassword() {
} }
android { android {
buildToolsVersion rootProject.buildToolsVersion
compileSdkVersion rootProject.compileSdkVersion compileSdkVersion rootProject.compileSdkVersion
defaultConfig { defaultConfig {
...@@ -54,8 +53,8 @@ mavenPublish { ...@@ -54,8 +53,8 @@ mavenPublish {
} }
dependencies { dependencies {
api 'androidx.core:core-ktx:1.0.2' api 'androidx.core:core-ktx:1.1.0-rc01'
api 'com.google.android.material:material:1.1.0-alpha06' api 'com.google.android.material:material:1.1.0-alpha07'
api "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion" api "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion"
testImplementation "junit:junit:$junitVersion" testImplementation "junit:junit:$junitVersion"
androidTestImplementation "androidx.test:runner:$androidTestVersion" androidTestImplementation "androidx.test:runner:$androidTestVersion"
......
...@@ -49,7 +49,7 @@ import androidx.core.os.bundleOf ...@@ -49,7 +49,7 @@ import androidx.core.os.bundleOf
*&lt;/manifest&gt;</pre> *&lt;/manifest&gt;</pre>
*/ */
abstract class NativePluginProvider : ContentProvider() { abstract class NativePluginProvider : ContentProvider() {
override fun getType(p0: Uri?): String = "application/x-elf" override fun getType(uri: Uri): String? = "application/x-elf"
override fun onCreate(): Boolean = true override fun onCreate(): Boolean = true
...@@ -61,7 +61,7 @@ abstract class NativePluginProvider : ContentProvider() { ...@@ -61,7 +61,7 @@ abstract class NativePluginProvider : ContentProvider() {
protected abstract fun populateFiles(provider: PathProvider) protected abstract fun populateFiles(provider: PathProvider)
override fun query(uri: Uri, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, override fun query(uri: Uri, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?,
sortOrder: String?): Cursor { sortOrder: String?): Cursor? {
check(selection == null && selectionArgs == null && sortOrder == null) check(selection == null && selectionArgs == null && sortOrder == null)
val result = MatrixCursor(projection) val result = MatrixCursor(projection)
populateFiles(PathProvider(uri, result)) populateFiles(PathProvider(uri, result))
...@@ -78,8 +78,8 @@ abstract class NativePluginProvider : ContentProvider() { ...@@ -78,8 +78,8 @@ abstract class NativePluginProvider : ContentProvider() {
*/ */
open fun getExecutable(): String = throw UnsupportedOperationException() open fun getExecutable(): String = throw UnsupportedOperationException()
abstract fun openFile(uri: Uri?): ParcelFileDescriptor abstract fun openFile(uri: Uri): ParcelFileDescriptor
override fun openFile(uri: Uri?, mode: String?): ParcelFileDescriptor { override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor {
check(mode == "r") check(mode == "r")
return openFile(uri) return openFile(uri)
} }
...@@ -90,8 +90,9 @@ abstract class NativePluginProvider : ContentProvider() { ...@@ -90,8 +90,9 @@ abstract class NativePluginProvider : ContentProvider() {
} }
// Methods that should not be used // Methods that should not be used
override fun insert(p0: Uri?, p1: ContentValues?): Uri = throw UnsupportedOperationException() override fun insert(uri: Uri, values: ContentValues?): Uri? = throw UnsupportedOperationException()
override fun update(p0: Uri?, p1: ContentValues?, p2: String?, p3: Array<out String>?): Int = override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<out String>?): Int =
throw UnsupportedOperationException()
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int =
throw UnsupportedOperationException() throw UnsupportedOperationException()
override fun delete(p0: Uri?, p1: String?, p2: Array<out String>?): Int = throw UnsupportedOperationException()
} }
...@@ -41,13 +41,14 @@ class PathProvider internal constructor(baseUri: Uri, private val cursor: Matrix ...@@ -41,13 +41,14 @@ class PathProvider internal constructor(baseUri: Uri, private val cursor: Matrix
var sub = to + file.name var sub = to + file.name
if (basePath.startsWith(sub)) if (file.isDirectory) { if (basePath.startsWith(sub)) if (file.isDirectory) {
sub += '/' sub += '/'
file.listFiles().forEach { addTo(it, sub, mode) } file.listFiles()!!.forEach { addTo(it, sub, mode) }
} else addPath(sub, mode) } else addPath(sub, mode)
return this return this
} }
fun addAt(file: File, at: String = "", mode: Int = 0b110100100): PathProvider { fun addAt(file: File, at: String = "", mode: Int = 0b110100100): PathProvider {
if (basePath.startsWith(at)) if (basePath.startsWith(at)) {
if (file.isDirectory) file.listFiles().forEach { addTo(it, at, mode) } else addPath(at, mode) if (file.isDirectory) file.listFiles()!!.forEach { addTo(it, at, mode) } else addPath(at, mode)
}
return this return this
} }
} }
...@@ -17,7 +17,6 @@ def getCurrentFlavor() { ...@@ -17,7 +17,6 @@ def getCurrentFlavor() {
} }
android { android {
buildToolsVersion rootProject.buildToolsVersion
compileSdkVersion rootProject.compileSdkVersion compileSdkVersion rootProject.compileSdkVersion
defaultConfig { defaultConfig {
applicationId "com.github.shadowsocks.tv" applicationId "com.github.shadowsocks.tv"
......
...@@ -184,11 +184,11 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo ...@@ -184,11 +184,11 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo
serviceMode = findPreference(Key.serviceMode)!! serviceMode = findPreference(Key.serviceMode)!!
shareOverLan = findPreference(Key.shareOverLan)!! shareOverLan = findPreference(Key.shareOverLan)!!
portProxy = findPreference(Key.portProxy)!! portProxy = findPreference(Key.portProxy)!!
portProxy.onBindEditTextListener = EditTextPreferenceModifiers.Port portProxy.setOnBindEditTextListener(EditTextPreferenceModifiers.Port)
portLocalDns = findPreference(Key.portLocalDns)!! portLocalDns = findPreference(Key.portLocalDns)!!
portLocalDns.onBindEditTextListener = EditTextPreferenceModifiers.Port portLocalDns.setOnBindEditTextListener(EditTextPreferenceModifiers.Port)
portTransproxy = findPreference(Key.portTransproxy)!! portTransproxy = findPreference(Key.portTransproxy)!!
portTransproxy.onBindEditTextListener = EditTextPreferenceModifiers.Port portTransproxy.setOnBindEditTextListener(EditTextPreferenceModifiers.Port)
serviceMode.onPreferenceChangeListener = onServiceModeChange serviceMode.onPreferenceChangeListener = onServiceModeChange
findPreference<Preference>(Key.about)!!.summary = getString(R.string.about_title, BuildConfig.VERSION_NAME) findPreference<Preference>(Key.about)!!.summary = getString(R.string.about_title, BuildConfig.VERSION_NAME)
...@@ -299,7 +299,7 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo ...@@ -299,7 +299,7 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo
try { try {
ProfileManager.createProfilesFromJson(data!!.datas.asSequence().map { ProfileManager.createProfilesFromJson(data!!.datas.asSequence().map {
context.contentResolver.openInputStream(it) context.contentResolver.openInputStream(it)
}, true) }.filterNotNull(), true)
} catch (e: Exception) { } catch (e: Exception) {
printLog(e) printLog(e)
Toast.makeText(context, e.readableMessage, Toast.LENGTH_SHORT).show() Toast.makeText(context, e.readableMessage, Toast.LENGTH_SHORT).show()
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment