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 {
javaVersion = JavaVersion.VERSION_1_8
kotlinVersion = '1.3.31'
minSdkVersion = 21
sdkVersion = 28
compileSdkVersion = 28
buildToolsVersion = '28.0.3'
sdkVersion = 29
compileSdkVersion = 29
junitVersion = '4.12'
androidTestVersion = '1.1.1'
androidEspressoVersion = '3.1.1'
androidTestVersion = '1.2.0'
androidEspressoVersion = '3.2.0'
versionCode = 4080050
versionName = '4.8.0-nightly'
resConfigs = ['es', 'fa', 'fr', 'ja', 'ko', 'ru', 'tr', 'zh-rCN', 'zh-rTW']
......@@ -27,7 +26,7 @@ buildscript {
}
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.google.gms:google-services:4.2.0'
classpath 'com.vanniktech:gradle-maven-publish-plugin:0.8.0'
......
......@@ -4,7 +4,6 @@ apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
android {
buildToolsVersion rootProject.buildToolsVersion
compileSdkVersion rootProject.compileSdkVersion
defaultConfig {
......@@ -48,14 +47,14 @@ androidExtensions {
experimental = true
}
def lifecycleVersion = '2.0.0'
def roomVersion = '2.0.0'
def lifecycleVersion = '2.1.0-beta01'
def roomVersion = '2.1.0-rc01'
dependencies {
api project(':plugin')
api "androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion"
api "androidx.lifecycle:lifecycle-extensions:$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.work:work-runtime-ktx:2.0.1'
api 'com.crashlytics.sdk.android:crashlytics:2.10.1'
......@@ -69,5 +68,5 @@ dependencies {
androidTestImplementation "androidx.room:room-testing:$roomVersion"
androidTestImplementation "androidx.test:runner:$androidTestVersion"
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 @@
android:icon="@mipmap/ic_launcher"
android:allowBackup="true"
android:backupAgent="com.github.shadowsocks.ConfigBackupHelper"
android:extractNativeLibs="true"
android:fullBackupContent="@xml/backup_descriptor"
android:fullBackupOnly="true"
android:label="@string/app_name"
......
......@@ -31,6 +31,7 @@ import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.net.ConnectivityManager
import android.os.Build
import android.os.UserManager
import androidx.annotation.RequiresApi
......@@ -61,6 +62,7 @@ object Core {
lateinit var app: Application
lateinit var configureIntent: (Context) -> PendingIntent
val connectivity by lazy { app.getSystemService<ConnectivityManager>()!! }
val packageInfo: PackageInfo by lazy { getPackageInfo(app.packageName) }
val deviceStorage by lazy { if (Build.VERSION.SDK_INT < 24) app else DeviceStorageApp(app) }
val analytics: FirebaseAnalytics by lazy { FirebaseAnalytics.getInstance(deviceStorage) }
......
......@@ -46,7 +46,6 @@ import com.google.firebase.analytics.FirebaseAnalytics
import kotlinx.coroutines.*
import java.io.File
import java.net.BindException
import java.net.InetAddress
import java.net.URL
import java.net.UnknownHostException
import java.util.*
......@@ -298,7 +297,7 @@ object BaseService {
listOfNotNull(data.proxy, data.udpFallback).forEach { it.trafficMonitor?.persistStats(it.profile.id) }
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()
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 {
private val EXECUTABLES = setOf(SS_LOCAL, REDSOCKS, TUN2SOCKS)
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 {
File(process, "cmdline").inputStream().bufferedReader().readText()
} catch (_: IOException) {
......
......@@ -84,8 +84,7 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro
if (profile.host.parseNumericAddress() == null) {
var retries = 0
while (true) try {
val io = GlobalScope.async(Dispatchers.IO) { service.resolver(profile.host) }
profile.host = io.await().firstOrNull()?.hostAddress ?: throw UnknownHostException()
profile.host = service.resolver(profile.host).firstOrNull()?.hostAddress ?: throw UnknownHostException()
return
} catch (e: UnknownHostException) {
// 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 {
if (active && Build.VERSION.SDK_INT >= 22) setUnderlyingNetworks(underlyingNetworks)
}
private val underlyingNetworks get() =
// clearing underlyingNetworks makes Android 9+ consider the network to be metered
if (Build.VERSION.SDK_INT >= 28 && metered) null else underlyingNetwork?.let { arrayOf(it) }
// clearing underlyingNetworks makes Android 9 consider the network to be metered
if (Build.VERSION.SDK_INT == 28 && metered) null else underlyingNetwork?.let { arrayOf(it) }
override fun onBind(intent: Intent) = when (intent.action) {
SERVICE_INTERFACE -> super<BaseVpnService>.onBind(intent)
......@@ -139,7 +139,7 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
}
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 startProcesses() {
......@@ -195,7 +195,10 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
metered = profile.metered
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()
this.conn = conn
......
......@@ -39,6 +39,7 @@ abstract class PrivateDatabase : RoomDatabase() {
Migration27,
Migration28
)
.enableMultiInstanceInvalidation()
.fallbackToDestructiveMigration()
.allowMainThreadQueries()
.build()
......
......@@ -36,6 +36,7 @@ abstract class PublicDatabase : RoomDatabase() {
.addMigrations(
Migration3
)
.enableMultiInstanceInvalidation()
.fallbackToDestructiveMigration()
.build()
}
......
......@@ -26,9 +26,8 @@ import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import android.os.Build
import androidx.core.content.getSystemService
import com.crashlytics.android.Crashlytics
import com.github.shadowsocks.Core.app
import com.github.shadowsocks.Core
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
......@@ -84,7 +83,7 @@ object DefaultNetworkListener {
suspend fun start(key: Any, listener: (Network?) -> Unit) = networkActor.send(NetworkMessage.Start(key, listener))
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 {
networkActor.send(this)
response.await()
......@@ -102,7 +101,6 @@ object DefaultNetworkListener {
}
private var fallback = false
private val connectivity = app.getSystemService<ConnectivityManager>()!!
private val request = NetworkRequest.Builder().apply {
addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
......@@ -119,16 +117,16 @@ object DefaultNetworkListener {
*/
private fun register() {
if (Build.VERSION.SDK_INT in 24..27) @TargetApi(24) {
connectivity.registerDefaultNetworkCallback(Callback)
Core.connectivity.registerDefaultNetworkCallback(Callback)
} else try {
fallback = false
// we want REQUEST here instead of LISTEN
connectivity.requestNetwork(request, Callback)
Core.connectivity.requestNetwork(request, Callback)
} catch (e: SecurityException) {
// known bug: https://stackoverflow.com/a/33509180/2245107
if (Build.VERSION.SDK_INT != 23) Crashlytics.logException(e)
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
if (forwardOnly) return@supervisorScope remote.await()
if (remoteDomainMatcher?.containsMatchIn(host) == true) return@supervisorScope remote.await()
val localResults = try {
withTimeout(TIMEOUT) { GlobalScope.async(Dispatchers.IO) { localResolver(host) }.await() }
withTimeout(TIMEOUT) { localResolver(host) }
} catch (_: TimeoutCancellationException) {
Crashlytics.log(Log.WARN, TAG, "Local resolving timed out, falling back to remote resolving")
return@supervisorScope remote.await()
......
......@@ -30,10 +30,10 @@ import com.github.shadowsocks.utils.signaturesCompat
abstract class ResolvedPlugin(protected val resolveInfo: ResolveInfo) : Plugin() {
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 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 trusted by lazy {
Core.getPackageInfo(packageName).signaturesCompat.any(PluginManager.trustedSignatures::contains)
......
......@@ -20,6 +20,7 @@
package com.github.shadowsocks.utils
import android.annotation.SuppressLint
import android.content.BroadcastReceiver
import android.content.ContentResolver
import android.content.Context
......@@ -36,25 +37,30 @@ import android.util.TypedValue
import androidx.annotation.AttrRes
import androidx.preference.Preference
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.InetAddress
import kotlin.coroutines.resume
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 {
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)
?: Os.inet_pton(OsConstants.AF_INET6, this)?.let { parseNumericAddress.invoke(null, this) as InetAddress }
fun String?.parseNumericAddress(): InetAddress? = Os.inet_pton(OsConstants.AF_INET, this)
?: 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)
computeIfAbsent(key) { value() } else this[key] ?: value().also { put(key, it) }
......
......@@ -10,7 +10,6 @@
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
android.enableJetifier=true
android.enableR8=true
android.enableR8.fullMode=true
android.useAndroidX=true
......
......@@ -17,7 +17,6 @@ def getCurrentFlavor() {
}
android {
buildToolsVersion rootProject.buildToolsVersion
compileSdkVersion rootProject.compileSdkVersion
defaultConfig {
applicationId "com.github.shadowsocks"
......@@ -61,7 +60,7 @@ androidExtensions {
dependencies {
implementation project(':core')
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.firebase:firebase-ads:17.2.1'
implementation 'com.takisoft.preferencex:preferencex-simplemenu:1.0.0'
......
......@@ -266,8 +266,8 @@ class AppManager : AppCompatActivity() {
return true
}
R.id.action_export_clipboard -> {
clipboard.primaryClip = ClipData.newPlainText(Key.individual,
"${DataStore.bypass}\n${DataStore.individual}")
clipboard.setPrimaryClip(ClipData.newPlainText(Key.individual,
"${DataStore.bypass}\n${DataStore.individual}"))
Snackbar.make(list, R.string.action_export_msg, Snackbar.LENGTH_LONG).show()
return true
}
......
......@@ -78,15 +78,15 @@ class GlobalSettingsPreferenceFragment : PreferenceFragmentCompat() {
tfo.summary = getString(R.string.tcp_fastopen_summary_unsupported, System.getProperty("os.version"))
}
hosts.onBindEditTextListener = EditTextPreferenceModifiers.Monospace
hosts.setOnBindEditTextListener(EditTextPreferenceModifiers.Monospace)
hosts.summaryProvider = HostsSummaryProvider
val serviceMode = findPreference<Preference>(Key.serviceMode)!!
val portProxy = findPreference<EditTextPreference>(Key.portProxy)!!
portProxy.onBindEditTextListener = EditTextPreferenceModifiers.Port
portProxy.setOnBindEditTextListener(EditTextPreferenceModifiers.Port)
val portLocalDns = findPreference<EditTextPreference>(Key.portLocalDns)!!
portLocalDns.onBindEditTextListener = EditTextPreferenceModifiers.Port
portLocalDns.setOnBindEditTextListener(EditTextPreferenceModifiers.Port)
val portTransproxy = findPreference<EditTextPreference>(Key.portTransproxy)!!
portTransproxy.onBindEditTextListener = EditTextPreferenceModifiers.Port
portTransproxy.setOnBindEditTextListener(EditTextPreferenceModifiers.Port)
val onServiceModeChange = Preference.OnPreferenceChangeListener { _, newValue ->
val (enabledLocalDns, enabledTransproxy) = when (newValue as String?) {
Key.modeProxy -> Pair(false, false)
......
......@@ -74,7 +74,7 @@ class ProfileConfigFragment : PreferenceFragmentCompat(),
val activity = requireActivity()
profileId = activity.intent.getLongExtra(Action.EXTRA_PROFILE_ID, -1L)
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
val serviceMode = DataStore.serviceMode
findPreference<Preference>(Key.remoteDns)!!.isEnabled = serviceMode != Key.modeProxy
......@@ -104,7 +104,7 @@ class ProfileConfigFragment : PreferenceFragmentCompat(),
}
true
}
pluginConfigure.onBindEditTextListener = EditTextPreferenceModifiers.Monospace
pluginConfigure.setOnBindEditTextListener(EditTextPreferenceModifiers.Monospace)
pluginConfigure.onPreferenceChangeListener = this
initPlugins()
receiver = Core.listenForPackageChanges(false) { initPlugins() }
......
......@@ -185,7 +185,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
true
}
R.id.action_export_clipboard -> {
clipboard.primaryClip = ClipData.newPlainText(null, this.item.toString())
clipboard.setPrimaryClip(ClipData.newPlainText(null, this.item.toString()))
true
}
else -> false
......@@ -381,7 +381,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
R.id.action_export_clipboard -> {
val profiles = ProfileManager.getAllProfiles()
(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
} else R.string.action_export_err).show()
true
......@@ -413,7 +413,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
try {
ProfileManager.createProfilesFromJson(data!!.datas.asSequence().map {
activity.contentResolver.openInputStream(it)
})
}.filterNotNull())
} catch (e: Exception) {
activity.snackbar(e.readableMessage).show()
}
......@@ -423,7 +423,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
try {
ProfileManager.createProfilesFromJson(data!!.datas.asSequence().map {
activity.contentResolver.openInputStream(it)
}, true)
}.filterNotNull(), true)
} catch (e: Exception) {
activity.snackbar(e.readableMessage).show()
}
......
......@@ -427,7 +427,7 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener,
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) {
......
......@@ -99,7 +99,7 @@ class BottomSheetPreferenceDialogFragment : PreferenceDialogFragmentCompat() {
}
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 }
......
......@@ -45,17 +45,20 @@ class StatsBar @JvmOverloads constructor(context: Context, attrs: AttributeSet?
private lateinit var txRateText: TextView
private lateinit var rxRateText: TextView
private val tester = ViewModelProviders.of(context as FragmentActivity).get<HttpsTest>()
private val behavior = object : Behavior() {
val threshold = context.resources.getDimensionPixelSize(R.dimen.stats_bar_scroll_threshold)
override fun onNestedScroll(coordinatorLayout: CoordinatorLayout, child: BottomAppBar, target: View,
dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int, dyUnconsumed: Int, type: Int,
consumed: IntArray) {
val dy = dyConsumed + dyUnconsumed
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, if (Math.abs(dy) >= threshold) dy else 0,
dxUnconsumed, 0, type, consumed)
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)
override fun onNestedScroll(coordinatorLayout: CoordinatorLayout, child: BottomAppBar, target: View,
dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int, dyUnconsumed: Int,
type: Int, consumed: IntArray) {
val dy = dyConsumed + dyUnconsumed
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed,
if (Math.abs(dy) >= threshold) dy else 0, dxUnconsumed, 0, type, consumed)
}
}
return behavior
}
override fun getBehavior() = behavior
override fun setOnClickListener(l: OnClickListener?) {
statusText = findViewById(R.id.status)
......
......@@ -14,7 +14,6 @@ def getRepositoryPassword() {
}
android {
buildToolsVersion rootProject.buildToolsVersion
compileSdkVersion rootProject.compileSdkVersion
defaultConfig {
......@@ -54,8 +53,8 @@ mavenPublish {
}
dependencies {
api 'androidx.core:core-ktx:1.0.2'
api 'com.google.android.material:material:1.1.0-alpha06'
api 'androidx.core:core-ktx:1.1.0-rc01'
api 'com.google.android.material:material:1.1.0-alpha07'
api "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion"
testImplementation "junit:junit:$junitVersion"
androidTestImplementation "androidx.test:runner:$androidTestVersion"
......
......@@ -49,7 +49,7 @@ import androidx.core.os.bundleOf
*&lt;/manifest&gt;</pre>
*/
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
......@@ -61,7 +61,7 @@ abstract class NativePluginProvider : ContentProvider() {
protected abstract fun populateFiles(provider: PathProvider)
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)
val result = MatrixCursor(projection)
populateFiles(PathProvider(uri, result))
......@@ -78,8 +78,8 @@ abstract class NativePluginProvider : ContentProvider() {
*/
open fun getExecutable(): String = throw UnsupportedOperationException()
abstract fun openFile(uri: Uri?): ParcelFileDescriptor
override fun openFile(uri: Uri?, mode: String?): ParcelFileDescriptor {
abstract fun openFile(uri: Uri): ParcelFileDescriptor
override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor {
check(mode == "r")
return openFile(uri)
}
......@@ -90,8 +90,9 @@ abstract class NativePluginProvider : ContentProvider() {
}
// Methods that should not be used
override fun insert(p0: Uri?, p1: ContentValues?): Uri = throw UnsupportedOperationException()
override fun update(p0: Uri?, p1: ContentValues?, p2: String?, p3: Array<out String>?): Int =
override fun insert(uri: Uri, values: ContentValues?): Uri? = throw UnsupportedOperationException()
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()
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
var sub = to + file.name
if (basePath.startsWith(sub)) if (file.isDirectory) {
sub += '/'
file.listFiles().forEach { addTo(it, sub, mode) }
file.listFiles()!!.forEach { addTo(it, sub, mode) }
} else addPath(sub, mode)
return this
}
fun addAt(file: File, at: String = "", mode: Int = 0b110100100): PathProvider {
if (basePath.startsWith(at))
if (file.isDirectory) file.listFiles().forEach { addTo(it, at, mode) } else addPath(at, mode)
if (basePath.startsWith(at)) {
if (file.isDirectory) file.listFiles()!!.forEach { addTo(it, at, mode) } else addPath(at, mode)
}
return this
}
}
......@@ -17,7 +17,6 @@ def getCurrentFlavor() {
}
android {
buildToolsVersion rootProject.buildToolsVersion
compileSdkVersion rootProject.compileSdkVersion
defaultConfig {
applicationId "com.github.shadowsocks.tv"
......
......@@ -184,11 +184,11 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo
serviceMode = findPreference(Key.serviceMode)!!
shareOverLan = findPreference(Key.shareOverLan)!!
portProxy = findPreference(Key.portProxy)!!
portProxy.onBindEditTextListener = EditTextPreferenceModifiers.Port
portProxy.setOnBindEditTextListener(EditTextPreferenceModifiers.Port)
portLocalDns = findPreference(Key.portLocalDns)!!
portLocalDns.onBindEditTextListener = EditTextPreferenceModifiers.Port
portLocalDns.setOnBindEditTextListener(EditTextPreferenceModifiers.Port)
portTransproxy = findPreference(Key.portTransproxy)!!
portTransproxy.onBindEditTextListener = EditTextPreferenceModifiers.Port
portTransproxy.setOnBindEditTextListener(EditTextPreferenceModifiers.Port)
serviceMode.onPreferenceChangeListener = onServiceModeChange
findPreference<Preference>(Key.about)!!.summary = getString(R.string.about_title, BuildConfig.VERSION_NAME)
......@@ -299,7 +299,7 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo
try {
ProfileManager.createProfilesFromJson(data!!.datas.asSequence().map {
context.contentResolver.openInputStream(it)
}, true)
}.filterNotNull(), true)
} catch (e: Exception) {
printLog(e)
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