Commit b00ae42a authored by Mygod's avatar Mygod

Merge branch 'master' into r

parents 1160f135 f2c83f97
......@@ -3,7 +3,7 @@ jobs:
build:
working_directory: ~/code
docker:
- image: shadowsocks/android-ndk-go:v1.0
- image: shadowsocks/android-ndk-go
environment:
GRADLE_OPTS: -Dorg.gradle.workers.max=1 -Dorg.gradle.daemon=false -Dkotlin.compiler.execution.strategy="in-process"
steps:
......
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
......
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
......
---
name: Questions/Support requests/Other
about: You should probably use the forum
title: ''
labels: question
assignees: ''
---
......
......@@ -16,4 +16,4 @@
branch = shadowsocks-android
[submodule "core/src/main/rust/shadowsocks-rust"]
path = core/src/main/rust/shadowsocks-rust
url = https://github.com/madeye/shadowsocks-rust
url = https://github.com/shadowsocks/shadowsocks-rust.git
......@@ -18,21 +18,21 @@ for Android TV ([beta](https://play.google.com/apps/testing/com.github.shadowsoc
* JDK 1.8
* Android SDK
- Android NDK
* Rust with Android targets installed
`$ rustup target install armv7-linux-androideabi aarch64-linux-android i686-linux-android x86_64-linux-android`
### BUILD
You can check whether the latest commit builds under UNIX environment by checking Travis status.
* Install prerequisites
* Clone the repo using `git clone --recurse-submodules <repo>` or update submodules using `git submodule update --init --recursive`
* Build it using Android Studio or gradle script
### BUILD WITH DOCKER
```bash
mkdir build
sudo chown 3434:3434 build
docker run --rm -v ${PWD}/build:/build circleci/android:api-28-ndk bash -c "cd /build; git clone https://github.com/shadowsocks/shadowsocks-android; cd shadowsocks-android; git submodule update --init --recursive; ./gradlew assembleDebug"
```
* Clone the repo using `git clone --recurse-submodules <repo>` or update submodules using `git submodule update --init --recursive`
* Run `docker run --rm -v ${PWD}:/build -w /build shadowsocks/android-ndk-go ./gradlew assembleDebug`
### CONTRIBUTING
......
......@@ -17,7 +17,7 @@ buildscript {
classpath(rootProject.extra.get("androidPlugin").toString())
classpath(kotlin("gradle-plugin", rootProject.extra.get("kotlinVersion").toString()))
classpath("com.google.android.gms:oss-licenses-plugin:0.10.2")
classpath("com.google.firebase:firebase-crashlytics-gradle:2.0.0-beta04")
classpath("com.google.firebase:firebase-crashlytics-gradle:2.1.1")
classpath("com.google.gms:google-services:4.3.3")
classpath("com.vanniktech:gradle-maven-publish-plugin:0.11.1")
classpath("gradle.plugin.org.mozilla.rust-android-gradle:plugin:0.8.3")
......@@ -28,6 +28,13 @@ allprojects {
apply(from = "${rootProject.projectDir}/repositories.gradle.kts")
}
tasks.register("clean", Delete::class) {
tasks.register<Delete>("clean") {
delete(rootProject.buildDir)
}
// skip uploading the mapping to Crashlytics
subprojects {
tasks.whenTaskAdded {
if (name.contains("uploadCrashlyticsMappingFile")) enabled = false
}
}
......@@ -17,7 +17,7 @@ private val Project.android get() = extensions.getByName<BaseExtension>("android
private val flavorRegex = "(assemble|generate)\\w*(Release|Debug)".toRegex()
val Project.currentFlavor get() = gradle.startParameter.taskRequests.toString().let { task ->
flavorRegex.matchEntire(task)?.groupValues?.get(2)?.toLowerCase(Locale.ROOT) ?: "debug".also {
flavorRegex.find(task)?.groupValues?.get(2)?.toLowerCase(Locale.ROOT) ?: "debug".also {
println("Warning: No match found for $task")
}
}
......@@ -50,10 +50,11 @@ fun Project.setupCore() {
setupCommon()
android.apply {
defaultConfig {
versionCode = 5000650
versionName = "5.0.6-nightly"
versionCode = 5010150
versionName = "5.1.1-nightly"
}
compileOptions.isCoreLibraryDesugaringEnabled = true
ndkVersion = "21.3.6528147"
}
dependencies.add("coreLibraryDesugaring", "com.android.tools:desugar_jdk_libs:1.0.5")
}
......@@ -74,6 +75,7 @@ fun Project.setupApp() {
proguardFile(getDefaultProguardFile("proguard-android.txt"))
}
}
lintOptions.disable("RemoveWorkManagerInitializer")
packagingOptions {
exclude("**/*.kotlin_*")
}
......
import com.android.build.gradle.internal.tasks.factory.dependsOn
plugins {
id("com.android.library")
id("org.mozilla.rust-android-gradle.rust-android")
......@@ -17,10 +19,9 @@ android {
arguments("-j${Runtime.getRuntime().availableProcessors()}")
}
javaCompileOptions.annotationProcessorOptions.arguments = mapOf(
javaCompileOptions.annotationProcessorOptions.arguments(mapOf(
"room.incremental" to "true",
"room.schemaLocation" to "$projectDir/schemas"
)
"room.schemaLocation" to "$projectDir/schemas"))
}
externalNativeBuild.ndkBuild.path("src/main/jni/Android.mk")
......@@ -30,42 +31,44 @@ android {
}
}
androidExtensions.isExperimental = true
cargo {
module = "src/main/rust/shadowsocks-rust"
libname = "sslocal"
targets = listOf("arm", "arm64", "x86", "x86_64")
profile = findProperty("CARGO_PROFILE")?.toString() ?: "release"
targetIncludes = arrayOf("lib$libname.so")
extraCargoBuildArguments = listOf("--bin", "sslocal")
features {
noDefaultBut(arrayOf(
"sodium",
"rc4",
"aes-cfb",
"aes-ctr",
"camellia-cfb",
"openssl-vendored",
"local-flow-stat",
"local-dns-relay"))
}
profile = findProperty("CARGO_PROFILE")?.toString() ?: currentFlavor
extraCargoBuildArguments = listOf("--bin", libname!!)
featureSpec.noDefaultBut(arrayOf(
"sodium",
"rc4",
"aes-cfb",
"aes-ctr",
"camellia-cfb",
"openssl-vendored",
"local-flow-stat",
"local-dns-relay"))
exec = { spec, toolchain ->
spec.environment("RUSTFLAGS",
"-C lto=no -C link-arg=-o -C link-arg=target/${toolchain.target}/$profile/lib$libname.so")
spec.environment("RUST_ANDROID_GRADLE_LINKER_WRAPPER_PY", "$projectDir/$module/../linker-wrapper.py")
spec.environment("RUST_ANDROID_GRADLE_TARGET", "target/${toolchain.target}/$profile/lib$libname.so")
}
}
tasks.whenTaskAdded {
when (name) {
"javaPreCompileDebug", "javaPreCompileRelease" -> dependsOn("cargoBuild")
"mergeDebugJniLibFolders", "mergeReleaseJniLibFolders" -> dependsOn("cargoBuild")
}
}
tasks.register<Exec>("cargoClean") {
executable("cargo") // cargo.cargoCommand
args("clean")
workingDir("$projectDir/${cargo.module}")
}
tasks.clean.dependsOn("cargoClean")
dependencies {
val coroutinesVersion = "1.3.5"
val coroutinesVersion = "1.3.6"
val roomVersion = "2.2.5"
val workVersion = "2.3.4"
val workVersion = "2.4.0-beta01"
api(project(":plugin"))
api("androidx.fragment:fragment-ktx:1.2.4")
......@@ -77,11 +80,11 @@ dependencies {
api("androidx.work:work-gcm:$workVersion")
api("com.google.android.gms:play-services-oss-licenses:17.0.0")
api("com.google.code.gson:gson:2.8.6")
api("com.google.firebase:firebase-analytics-ktx:17.3.0")
api("com.google.firebase:firebase-config-ktx:19.1.3")
api("com.google.firebase:firebase-crashlytics:17.0.0-beta04")
api("com.google.firebase:firebase-analytics-ktx:17.4.3")
api("com.google.firebase:firebase-config-ktx:19.1.4")
api("com.google.firebase:firebase-crashlytics:17.0.1")
api("com.jakewharton.timber:timber:4.7.1")
api("dnsjava:dnsjava:3.0.2")
api("dnsjava:dnsjava:3.1.0")
api("org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion")
api("org.jetbrains.kotlinx:kotlinx-coroutines-play-services:$coroutinesVersion")
kapt("androidx.room:room-compiler:$roomVersion")
......
<?xml version="1.0" encoding="UTF-8"?>
<lint>
<issue id="BadConfigurationProvider" severity="informational" />
<issue id="ImpliedQuantity" severity="warning" />
<issue id="ExtraTranslation" severity="warning" />
<issue id="MissingDefaultResource" severity="warning" />
......
......@@ -32,6 +32,7 @@
android:extractNativeLibs="true"
android:fullBackupContent="@xml/backup_descriptor"
android:fullBackupOnly="true"
android:hasFragileUserData="true"
android:label="@string/app_name"
android:supportsRtl="true"
android:networkSecurityConfig="@xml/network_security_config"
......@@ -113,52 +114,30 @@
tools:node="remove"/>
<service android:name="androidx.work.impl.background.systemalarm.SystemAlarmService"
android:process=":bg"
android:directBootAware="true"
tools:ignore="Instantiatable"
tools:replace="android:directBootAware"/>
tools:ignore="Instantiatable"/>
<service android:name="androidx.work.impl.background.systemjob.SystemJobService"
android:process=":bg"
android:directBootAware="true"
tools:replace="android:directBootAware"/>
android:process=":bg"/>
<service android:name="androidx.work.impl.foreground.SystemForegroundService"
android:process=":bg"
android:directBootAware="true"
tools:ignore="Instantiatable"
tools:replace="android:directBootAware"/>
tools:ignore="Instantiatable"/>
<receiver android:name="androidx.work.impl.utils.ForceStopRunnable$BroadcastReceiver"
android:process=":bg"
android:directBootAware="true"
tools:replace="android:directBootAware"/>
android:process=":bg"/>
<receiver android:name="androidx.work.impl.background.systemalarm.ConstraintProxy$BatteryChargingProxy"
android:process=":bg"
android:directBootAware="true"
tools:replace="android:directBootAware"/>
android:process=":bg"/>
<receiver android:name="androidx.work.impl.background.systemalarm.ConstraintProxy$BatteryNotLowProxy"
android:process=":bg"
android:directBootAware="true"
tools:replace="android:directBootAware"/>
android:process=":bg"/>
<receiver android:name="androidx.work.impl.background.systemalarm.ConstraintProxy$StorageNotLowProxy"
android:process=":bg"
android:directBootAware="true"
tools:replace="android:directBootAware"/>
android:process=":bg"/>
<receiver android:name="androidx.work.impl.background.systemalarm.ConstraintProxy$NetworkStateProxy"
android:process=":bg"
android:directBootAware="true"
tools:replace="android:directBootAware"/>
android:process=":bg"/>
<receiver android:name="androidx.work.impl.background.systemalarm.RescheduleReceiver"
android:process=":bg"
android:directBootAware="true"
tools:replace="android:directBootAware"/>
android:process=":bg"/>
<receiver android:name="androidx.work.impl.background.systemalarm.ConstraintProxyUpdateReceiver"
android:process=":bg"
android:directBootAware="true"
tools:replace="android:directBootAware"/>
android:process=":bg"/>
<!-- Used for API < 23. https://android.googlesource.com/platform/frameworks/support/+/androidx-master-dev/work/workmanager-gcm/src/main/AndroidManifest.xml -->
<service android:name="androidx.work.impl.background.gcm.WorkManagerGcmService"
android:process=":bg"
android:directBootAware="true"
tools:ignore="Instantiatable"
tools:replace="android:directBootAware"/>
tools:ignore="Instantiatable"/>
</application>
</manifest>
......@@ -26,8 +26,6 @@ import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.UserManager
import androidx.core.content.getSystemService
import com.github.shadowsocks.Core.app
import com.github.shadowsocks.preference.DataStore
......@@ -50,8 +48,7 @@ class BootReceiver : BroadcastReceiver() {
val doStart = when (intent.action) {
Intent.ACTION_BOOT_COMPLETED -> !DataStore.directBootAware
Intent.ACTION_LOCKED_BOOT_COMPLETED -> DataStore.directBootAware
else -> DataStore.directBootAware ||
Build.VERSION.SDK_INT >= 24 && app.getSystemService<UserManager>()?.isUserUnlocked != false
else -> DataStore.directBootAware || Build.VERSION.SDK_INT >= 24 && Core.user.isUserUnlocked
}
if (doStart) Core.startService()
}
......
......@@ -37,7 +37,6 @@ import androidx.annotation.VisibleForTesting
import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService
import androidx.work.Configuration
import androidx.work.WorkManager
import com.github.shadowsocks.acl.Acl
import com.github.shadowsocks.aidl.ShadowsocksConnection
import com.github.shadowsocks.core.BuildConfig
......@@ -62,7 +61,7 @@ import java.io.File
import java.io.IOException
import kotlin.reflect.KClass
object Core {
object Core : Configuration.Provider {
lateinit var app: Application
@VisibleForTesting set
lateinit var configureIntent: (Context) -> PendingIntent
......@@ -70,6 +69,7 @@ object Core {
val clipboard by lazy { app.getSystemService<ClipboardManager>()!! }
val connectivity by lazy { app.getSystemService<ConnectivityManager>()!! }
val notification by lazy { app.getSystemService<NotificationManager>()!! }
val user by lazy { app.getSystemService<UserManager>()!! }
val packageInfo: PackageInfo by lazy { getPackageInfo(app.packageName) }
val deviceStorage by lazy { if (Build.VERSION.SDK_INT < 24) app else DeviceStorageApp(app) }
val directBootSupported by lazy {
......@@ -80,7 +80,7 @@ object Core {
val activeProfileIds get() = ProfileManager.getProfile(DataStore.profileId).let {
if (it == null) emptyList() else listOfNotNull(it.id, it.udpFallback)
}
val currentProfile: Pair<Profile, Profile?>? get() {
val currentProfile: ProfileManager.ExpandedProfile? get() {
if (DataStore.directBootAware) DirectBoot.getDeviceProfile()?.apply { return this }
return ProfileManager.expand(ProfileManager.getProfile(DataStore.profileId) ?: return null)
}
......@@ -121,14 +121,11 @@ object Core {
}
}
})
WorkManager.initialize(deviceStorage, Configuration.Builder().apply {
setExecutor { GlobalScope.launch { it.run() } }
setTaskExecutor { GlobalScope.launch { it.run() } }
}.build())
// handle data restored/crash
if (Build.VERSION.SDK_INT >= 24 && DataStore.directBootAware &&
app.getSystemService<UserManager>()?.isUserUnlocked == true) DirectBoot.flushTrafficStats()
if (Build.VERSION.SDK_INT >= 24 && DataStore.directBootAware && user.isUserUnlocked) {
DirectBoot.flushTrafficStats()
}
if (DataStore.publicStore.getLong(Key.assetUpdateTime, -1) != packageInfo.lastUpdateTime) {
val assetManager = app.assets
try {
......@@ -143,6 +140,12 @@ object Core {
updateNotificationChannels()
}
override fun getWorkManagerConfiguration() = Configuration.Builder().apply {
setMinimumLoggingLevel(if (BuildConfig.DEBUG) Log.VERBOSE else Log.INFO)
setExecutor { GlobalScope.launch { it.run() } }
setTaskExecutor { GlobalScope.launch { it.run() } }
}.build()
fun updateNotificationChannels() {
if (Build.VERSION.SDK_INT >= 26) @RequiresApi(26) {
notification.createNotificationChannels(listOf(
......
......@@ -67,7 +67,7 @@ class UrlImportActivity : AppCompatActivity() {
}
private fun handleShareIntent() = intent.data?.toString()?.let { sharedStr ->
val profiles = Profile.findAllUrls(sharedStr, Core.currentProfile?.first).toList()
val profiles = Profile.findAllUrls(sharedStr, Core.currentProfile?.main).toList()
if (profiles.isEmpty()) null else ImportProfilesDialogFragment().withArg(ProfilesArg(profiles))
}
}
......@@ -21,8 +21,10 @@
package com.github.shadowsocks.acl
import android.content.Context
import android.os.Build
import androidx.work.*
import com.github.shadowsocks.Core
import com.github.shadowsocks.Core.app
import com.github.shadowsocks.utils.useCancellable
import timber.log.Timber
import java.io.IOException
......@@ -34,16 +36,19 @@ class AclSyncer(context: Context, workerParams: WorkerParameters) : CoroutineWor
companion object {
private const val KEY_ROUTE = "route"
fun schedule(route: String) = WorkManager.getInstance(Core.deviceStorage).enqueueUniqueWork(
route, ExistingWorkPolicy.REPLACE, OneTimeWorkRequestBuilder<AclSyncer>().run {
setInputData(Data.Builder().putString(KEY_ROUTE, route).build())
setConstraints(Constraints.Builder()
.setRequiredNetworkType(NetworkType.UNMETERED)
.setRequiresCharging(true)
.build())
setInitialDelay(10, TimeUnit.SECONDS)
build()
})
fun schedule(route: String) {
if (Build.VERSION.SDK_INT >= 24 && !Core.user.isUserUnlocked) return // work does not support this
WorkManager.getInstance(app).enqueueUniqueWork(
route, ExistingWorkPolicy.REPLACE, OneTimeWorkRequestBuilder<AclSyncer>().run {
setInputData(Data.Builder().putString(KEY_ROUTE, route).build())
setConstraints(Constraints.Builder()
.setRequiredNetworkType(NetworkType.UNMETERED)
.setRequiresCharging(true)
.build())
setInitialDelay(10, TimeUnit.SECONDS)
build()
})
}
}
override suspend fun doWork(): Result = try {
......
......@@ -24,8 +24,10 @@ import android.app.Service
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.*
import androidx.core.content.getSystemService
import android.os.Build
import android.os.IBinder
import android.os.RemoteCallbackList
import android.os.RemoteException
import com.github.shadowsocks.BootReceiver
import com.github.shadowsocks.Core
import com.github.shadowsocks.Core.app
......@@ -238,13 +240,13 @@ object BaseService {
val isVpnService get() = false
suspend fun startProcesses() {
val configRoot = (if (Build.VERSION.SDK_INT < 24 || app.getSystemService<UserManager>()
?.isUserUnlocked != false) app else Core.deviceStorage).noBackupFilesDir
val context = if (Build.VERSION.SDK_INT < 24 || Core.user.isUserUnlocked) app else Core.deviceStorage
val configRoot = context.noBackupFilesDir
val udpFallback = data.udpFallback
data.proxy!!.start(this,
File(Core.deviceStorage.noBackupFilesDir, "stat_main"),
File(configRoot, CONFIG_FILE),
if (udpFallback == null) "-U" else null)
if (udpFallback == null && data.proxy?.plugin == null) "-U" else null)
if (udpFallback?.plugin != null) throw ExpectedExceptionWrapper(IllegalStateException(
"UDP fallback cannot have plugins"))
udpFallback?.start(this,
......@@ -321,15 +323,15 @@ object BaseService {
fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val data = data
if (data.state != State.Stopped) return Service.START_NOT_STICKY
val profilePair = Core.currentProfile
val expanded = Core.currentProfile
this as Context
if (profilePair == null) {
if (expanded == null) {
// gracefully shutdown: https://stackoverflow.com/q/47337857/2245107
data.notification = createNotification("")
stopRunner(false, getString(R.string.profile_empty))
return Service.START_NOT_STICKY
}
val (profile, fallback) = profilePair
val (profile, fallback) = expanded
profile.name = profile.formattedName // save name for later queries
val proxy = ProxyInstance(profile)
data.proxy = proxy
......
......@@ -4,6 +4,7 @@ import android.net.LocalSocket
import com.github.shadowsocks.Core
import com.github.shadowsocks.net.ConcurrentLocalSocketListener
import com.github.shadowsocks.net.DnsResolverCompat
import com.github.shadowsocks.utils.readableMessage
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.TimeoutCancellationException
......@@ -23,8 +24,11 @@ class LocalDnsWorker(private val resolver: suspend (ByteArray) -> ByteArray) : C
launch {
socket.use {
val input = DataInputStream(socket.inputStream)
val query = ByteArray(input.readUnsignedShort())
input.read(query)
val query = try {
ByteArray(input.readUnsignedShort()).also { input.read(it) }
} catch (e: IOException) { // connection early close possibly due to resolving timeout
return@use Timber.d(e)
}
try {
resolver(query)
} catch (e: Exception) {
......@@ -46,7 +50,9 @@ class LocalDnsWorker(private val resolver: suspend (ByteArray) -> ByteArray) : C
val output = DataOutputStream(socket.outputStream)
output.writeShort(response.size)
output.write(response)
} catch (_: IOException) { } // connection early close possibly due to resolving timeout
} catch (e: IOException) {
Timber.d(e.readableMessage)
}
}
}
}
......
......@@ -86,8 +86,8 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro
if (profile.host.parseNumericAddress() == null) {
profile.host = try {
service.resolver(profile.host).firstOrNull()
} catch (_: IOException) {
null
} catch (e: IOException) {
throw UnknownHostException().initCause(e)
}?.hostAddress ?: throw UnknownHostException()
}
}
......@@ -121,7 +121,7 @@ class ProxyInstance(val profile: Profile, private val route: String = profile.ro
}.let { dns ->
cmd += arrayListOf(
"--dns-relay", "${DataStore.listenAddress}:${DataStore.portLocalDns}",
"--remote-dns", "${dns.host!!}:${if (dns.port < 0) 53 else dns.port}")
"--remote-dns", "${dns.host ?: "0.0.0.0"}:${if (dns.port < 0) 53 else dns.port}")
}
if (route != Acl.ALL) {
......
......@@ -96,7 +96,7 @@ class TrafficMonitor(statFile: File) {
ProfileManager.updateProfile(profile)
} catch (e: IOException) {
if (!DataStore.directBootAware) throw e // we should only reach here because we're in direct boot
val profile = DirectBoot.getDeviceProfile()!!.toList().filterNotNull().single { it.id == id }
val profile = DirectBoot.getDeviceProfile()!!.toList().single { it.id == id }
profile.tx += current.txTotal
profile.rx += current.rxTotal
profile.dirty = true
......
......@@ -29,6 +29,7 @@ import android.net.Network
import android.os.Build
import android.os.ParcelFileDescriptor
import android.system.ErrnoException
import android.system.Os
import android.system.OsConstants
import com.github.shadowsocks.Core
import com.github.shadowsocks.VpnRequestActivity
......@@ -40,7 +41,6 @@ import com.github.shadowsocks.net.DnsResolverCompat
import com.github.shadowsocks.net.Subnet
import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.Key
import com.github.shadowsocks.utils.closeQuietly
import com.github.shadowsocks.utils.int
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
......@@ -59,34 +59,42 @@ class VpnService : BaseVpnService(), BaseService.Interface {
private const val PRIVATE_VLAN4_ROUTER = "172.19.0.2"
private const val PRIVATE_VLAN6_CLIENT = "fdfe:dcba:9876::1"
private const val PRIVATE_VLAN6_ROUTER = "fdfe:dcba:9876::2"
private fun <T> FileDescriptor.use(block: (FileDescriptor) -> T) = try {
block(this)
} finally {
try {
Os.close(this)
} catch (_: ErrnoException) { }
}
}
private inner class ProtectWorker : ConcurrentLocalSocketListener("ShadowsocksVpnThread",
File(Core.deviceStorage.noBackupFilesDir, "protect_path")) {
override fun acceptInternal(socket: LocalSocket) {
socket.inputStream.read()
val fd = socket.ancillaryFileDescriptors!!.single()!!
try {
socket.outputStream.write(if (underlyingNetwork.let { network ->
if (network != null) try {
DnsResolverCompat.bindSocket(network, fd)
return@let true
} catch (e: IOException) {
when ((e.cause as? ErrnoException)?.errno) {
// also suppress ENONET (Machine is not on the network)
OsConstants.EPERM, 64 -> Timber.d(e)
else -> Timber.w(e)
}
return@let false
} catch (e: ReflectiveOperationException) {
check(Build.VERSION.SDK_INT < 23)
Timber.w(e)
}
protect(fd.int)
}) 0 else 1)
} finally {
fd.closeQuietly()
val success = socket.ancillaryFileDescriptors!!.single()!!.use { fd ->
underlyingNetwork.let { network ->
if (network != null) try {
DnsResolverCompat.bindSocket(network, fd)
return@let true
} catch (e: IOException) {
when ((e.cause as? ErrnoException)?.errno) {
// also suppress ENONET (Machine is not on the network)
OsConstants.EPERM, OsConstants.EACCES, 64 -> Timber.d(e)
else -> Timber.w(e)
}
return@let false
} catch (e: ReflectiveOperationException) {
check(Build.VERSION.SDK_INT < 23)
Timber.w(e)
}
protect(fd.int)
}
}
try {
socket.outputStream.write(if (success) 0 else 1)
} catch (_: IOException) { } // ignore connection early close
}
}
......
......@@ -46,6 +46,10 @@ object ProfileManager {
}
var listener: Listener? = null
data class ExpandedProfile(val main: Profile, val udpFallback: Profile?) {
fun toList() = listOfNotNull(main, udpFallback)
}
@Throws(SQLException::class)
fun createProfile(profile: Profile = Profile()): Profile {
profile.id = 0
......@@ -59,7 +63,7 @@ object ProfileManager {
val profiles = if (replace) getAllProfiles()?.associateBy { it.formattedAddress } else null
val feature = if (replace) {
profiles?.values?.singleOrNull { it.id == DataStore.profileId }
} else Core.currentProfile?.first
} else Core.currentProfile?.main
val lazyClear = lazy { clear() }
jsons.asIterable().forEachTry { json ->
Profile.parseJson(JsonStreamParser(json.bufferedReader()).asSequence().single(), feature) {
......@@ -99,7 +103,7 @@ object ProfileManager {
}
@Throws(IOException::class)
fun expand(profile: Profile): Pair<Profile, Profile?> = Pair(profile, profile.udpFallback?.let { getProfile(it) })
fun expand(profile: Profile) = ExpandedProfile(profile, profile.udpFallback?.let { getProfile(it) })
@Throws(SQLException::class)
fun delProfile(id: Long) {
......
......@@ -26,9 +26,7 @@ import android.net.DnsResolver
import android.net.Network
import android.os.Build
import android.os.CancellationSignal
import android.os.Looper
import android.system.ErrnoException
import android.system.Os
import com.github.shadowsocks.Core
import com.github.shadowsocks.utils.int
import kotlinx.coroutines.*
......@@ -70,6 +68,7 @@ sealed class DnsResolverCompat {
fun prepareDnsResponse(request: Message) = Message(request.header.id).apply {
header.setFlag(Flags.QR.toInt()) // this is a response
header.setFlag(Flags.RA.toInt()) // recursion available
if (request.header.getFlag(Flags.RD.toInt())) header.setFlag(Flags.RD.toInt())
request.question?.also { addRecord(it, Section.QUESTION) }
}
......@@ -77,8 +76,6 @@ sealed class DnsResolverCompat {
@Throws(IOException::class)
abstract fun bindSocket(network: Network, socket: FileDescriptor)
internal open suspend fun connectUdp(fd: FileDescriptor, address: InetAddress, port: Int = 0) =
Os.connect(fd, address, port)
abstract suspend fun resolve(network: Network, host: String): Array<InetAddress>
abstract suspend fun resolveOnActiveNetwork(host: String): Array<InetAddress>
abstract suspend fun resolveRaw(network: Network, query: ByteArray): ByteArray
......@@ -99,12 +96,6 @@ sealed class DnsResolverCompat {
throw ErrnoException(message, -err).rethrowAsSocketException()
}
override suspend fun connectUdp(fd: FileDescriptor, address: InetAddress, port: Int) {
if (Looper.getMainLooper().thread == Thread.currentThread()) withContext(Dispatchers.IO) { // #2405
super.connectUdp(fd, address, port)
} else super.connectUdp(fd, address, port)
}
/**
* This dispatcher is used for noncancellable possibly-forever-blocking operations in network IO.
*
......@@ -120,7 +111,7 @@ sealed class DnsResolverCompat {
override suspend fun resolveOnActiveNetwork(host: String) =
GlobalScope.async(unboundedIO) { InetAddress.getAllByName(host) }.await()
private suspend fun resolveRaw(query: ByteArray,
private suspend fun resolveRaw(query: ByteArray, networkSpecified: Boolean = true,
hostResolver: suspend (String) -> Array<InetAddress>): ByteArray {
val request = try {
Message(query)
......@@ -135,11 +126,23 @@ sealed class DnsResolverCompat {
val isIpv6 = when (val type = question?.type) {
Type.A -> false
Type.AAAA -> true
Type.PTR -> {
// Android does not provide a PTR lookup API for Network prior to Android 10
if (networkSpecified) throw IOException(UnsupportedOperationException("Network unspecified"))
val ip = try {
ReverseMap.fromName(question.name)
} catch (e: IOException) {
throw UnsupportedOperationException(e) // unrecognized PTR name
}
val hostname = Name.fromString(GlobalScope.async(unboundedIO) { ip.hostName }.await())
return prepareDnsResponse(request).apply {
addRecord(PTRRecord(question.name, DClass.IN, TTL, hostname), Section.ANSWER)
}.toWire()
}
else -> throw UnsupportedOperationException("Unsupported query type $type")
}
val host = question.name.canonicalize().toString(true)
return prepareDnsResponse(request).apply {
header.setFlag(Flags.RA.toInt()) // recursion available
for (address in hostResolver(host).asIterable().run {
if (isIpv6) filterIsInstance<Inet6Address>() else filterIsInstance<Inet4Address>()
}) addRecord(when (address) {
......@@ -152,7 +155,7 @@ sealed class DnsResolverCompat {
override suspend fun resolveRaw(network: Network, query: ByteArray) =
resolveRaw(query) { resolve(network, it) }
override suspend fun resolveRawOnActiveNetwork(query: ByteArray) =
resolveRaw(query, this::resolveOnActiveNetwork)
resolveRaw(query, false, this::resolveOnActiveNetwork)
}
@TargetApi(23)
......
......@@ -84,10 +84,7 @@ class HttpsTest : ViewModel() {
fun testConnection() {
cancelTest()
status.value = Status.Testing
val url = URL("https", when ((Core.currentProfile ?: return).first.route) {
Acl.CHINALIST -> "www.qualcomm.cn"
else -> "www.google.com"
}, "/generate_204")
val url = URL("https://cp.cloudflare.com")
val conn = (if (DataStore.serviceMode != Key.modeVpn) {
url.openConnection(Proxy(Proxy.Type.SOCKS, DataStore.proxyAddress))
} else url.openConnection()) as HttpURLConnection
......
......@@ -22,6 +22,7 @@ package com.github.shadowsocks.plugin
import android.content.Intent
import android.content.pm.PackageManager
import android.widget.Toast
import com.github.shadowsocks.Core.app
class PluginList : ArrayList<Plugin>() {
......@@ -33,7 +34,14 @@ class PluginList : ArrayList<Plugin>() {
val lookup = mutableMapOf<String, Plugin>().apply {
for (plugin in this@PluginList) {
fun check(old: Plugin?) = check(old == null || old === plugin)
fun check(old: Plugin?) {
if (old != null && old !== plugin) {
val packages = this@PluginList.filter { it.id == plugin.id }.joinToString { it.packageName }
val message = "Conflicting plugins found from: $packages"
Toast.makeText(app, message, Toast.LENGTH_LONG).show()
throw IllegalStateException(message)
}
}
check(put(plugin.id, plugin))
for (alias in plugin.idAliases) check(put(alias, plugin))
}
......
......@@ -33,6 +33,7 @@ import android.net.Uri
import android.os.Build
import android.system.Os
import android.util.Base64
import android.widget.Toast
import androidx.core.os.bundleOf
import com.github.shadowsocks.Core
import com.github.shadowsocks.Core.app
......@@ -143,6 +144,11 @@ object PluginManager {
val providers = app.packageManager.queryIntentContentProviders(
Intent(PluginContract.ACTION_NATIVE_PLUGIN, buildUri(configuration.selected)), flags)
if (providers.isEmpty()) return null
if (providers.size > 1) {
val message = "Conflicting plugins found from: ${providers.joinToString { it.providerInfo.packageName }}"
Toast.makeText(app, message, Toast.LENGTH_LONG).show()
throw IllegalStateException(message)
}
val provider = providers.single().providerInfo
val options = configuration.getOptions { provider.loadString(PluginContract.METADATA_KEY_DEFAULT_CONFIG) }
var failure: Throwable? = null
......
......@@ -21,8 +21,8 @@ object DirectBoot : BroadcastReceiver() {
private val file = File(Core.deviceStorage.noBackupFilesDir, "directBootProfile")
private var registered = false
fun getDeviceProfile(): Pair<Profile, Profile?>? = try {
ObjectInputStream(file.inputStream()).use { it.readObject() as? Pair<Profile, Profile?> }
fun getDeviceProfile(): ProfileManager.ExpandedProfile? = try {
ObjectInputStream(file.inputStream()).use { it.readObject() as? ProfileManager.ExpandedProfile }
} catch (_: IOException) { null }
fun clean() {
......
......@@ -28,7 +28,6 @@ import android.graphics.BitmapFactory
import android.graphics.ImageDecoder
import android.net.Uri
import android.os.Build
import android.system.ErrnoException
import android.system.Os
import android.system.OsConstants
import android.util.TypedValue
......@@ -66,10 +65,6 @@ val Throwable.readableMessage get() = localizedMessage ?: javaClass.name
private val getInt = FileDescriptor::class.java.getDeclaredMethod("getInt$")
val FileDescriptor.int get() = getInt.invoke(this) as Int
fun FileDescriptor.closeQuietly() = try {
Os.close(this)
} catch (_: ErrnoException) { }
private val parseNumericAddress by lazy @SuppressLint("SoonBlockedPrivateApi") {
InetAddress::class.java.getDeclaredMethod("parseNumericAddress", String::class.java).apply {
isAccessible = true
......
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="enc_method_entry" translatable="false">
<item>PLAIN</item>
<item>NONE</item>
<item>RC4-MD5</item>
<item>AES-128-CFB</item>
<item>AES-192-CFB</item>
......@@ -24,7 +24,7 @@
</string-array>
<string-array name="enc_method_value" translatable="false">
<item>plain</item>
<item>none</item>
<item>rc4-md5</item>
<item>aes-128-cfb</item>
<item>aes-192-cfb</item>
......
from __future__ import absolute_import, print_function, unicode_literals
import os
import pipes
import shutil
import subprocess
import sys
args = [os.environ['RUST_ANDROID_GRADLE_CC'], os.environ['RUST_ANDROID_GRADLE_CC_LINK_ARG']] + sys.argv[1:]
# This only appears when the subprocess call fails, but it's helpful then.
printable_cmd = ' '.join(pipes.quote(arg) for arg in args)
print(printable_cmd)
code = subprocess.call(args)
if code == 0:
shutil.copyfile(sys.argv[sys.argv.index('-o') + 1], os.environ['RUST_ANDROID_GRADLE_TARGET'])
sys.exit(code)
Subproject commit 246b6c48bce6d4a83af046bd3feb4843f772b9d2
Subproject commit ce1e58641d502ec128806efa898ebf2716461132
......@@ -19,10 +19,8 @@ complexity:
ignoreSingleWhenExpression: false
ignoreSimpleWhenEntries: false
ignoreNestingFunctions: false
nestingFunctions: run,let,apply,with,also,use,forEach,isNotNull,ifNull
LabeledExpression:
active: false
ignoredLabels: ''
LargeClass:
active: true
threshold: 600
......@@ -35,6 +33,7 @@ complexity:
constructorThreshold: 7
ignoreDefaultParameters: true
ignoreDataClasses: true
ignoreAnnotated: []
MethodOverloading:
active: false
NestedBlockDepth:
......@@ -42,14 +41,14 @@ complexity:
threshold: 4
StringLiteralDuplication:
active: true
excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt'
excludes: ['**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt']
threshold: 3
ignoreAnnotation: true
excludeStringsWithLessThan5Characters: true
ignoreStringsRegex: '$^'
TooManyFunctions:
active: true
excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt'
excludes: ['**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt']
thresholdInFiles: 11
thresholdInClasses: 11
thresholdInInterfaces: 11
......@@ -105,7 +104,7 @@ exceptions:
active: true
ExceptionRaisedInUnexpectedLocation:
active: true
methodNames: 'toString,hashCode,equals,finalize'
methodNames: [toString, hashCode, equals, finalize]
InstanceOfCheckForException:
active: false
NotImplementedDeclaration:
......@@ -119,7 +118,11 @@ exceptions:
ignoreLabeled: true
SwallowedException:
active: true
ignoredExceptionTypes: 'InterruptedException,NumberFormatException,ParseException,MalformedURLException'
ignoredExceptionTypes:
- InterruptedException
- NumberFormatException
- ParseException
- MalformedURLException
allowedExceptionNameRegex: '^(_|(ignore|expected).*)'
ThrowingExceptionFromFinally:
active: false
......@@ -127,12 +130,15 @@ exceptions:
active: true
ThrowingExceptionsWithoutMessageOrCause:
active: true
exceptions: 'IllegalArgumentException,IllegalStateException,IOException'
exceptions:
- IllegalArgumentException
- IllegalStateException
- IOException
ThrowingNewInstanceOfSameException:
active: true
TooGenericExceptionCaught:
active: true
excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt'
excludes: ['**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt']
exceptionNames:
- ArrayIndexOutOfBoundsException
- Error
......@@ -259,37 +265,37 @@ naming:
ClassNaming:
active: true
classPattern: '[A-Z$][a-zA-Z0-9$]*'
excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt'
excludes: ['**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt']
ConstructorParameterNaming:
active: true
excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt'
excludes: ['**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt']
parameterPattern: '[a-z][A-Za-z0-9]*'
privateParameterPattern: '[a-z][A-Za-z0-9]*'
excludeClassPattern: '$^'
ignoreOverridden: true
EnumNaming:
active: true
excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt'
excludes: ['**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt']
enumEntryPattern: '^[A-Z][_a-zA-Z0-9]*'
ForbiddenClassName:
active: true
excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt'
forbiddenName: ''
excludes: ['**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt']
forbiddenName: []
FunctionMaxLength:
active: true
excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt'
excludes: ['**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt']
maximumFunctionNameLength: 30
FunctionMinLength:
active: false
FunctionNaming:
active: true
excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt'
excludes: ['**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt']
functionPattern: '^([a-z$][a-zA-Z$0-9]*)|(`.*`)$'
excludeClassPattern: '$^'
ignoreOverridden: true
FunctionParameterNaming:
active: true
excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt'
excludes: ['**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt']
parameterPattern: '[a-z][A-Za-z0-9]*'
excludeClassPattern: '$^'
ignoreOverridden: true
......@@ -303,29 +309,29 @@ naming:
active: false
ObjectPropertyNaming:
active: true
excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt'
excludes: ['**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt']
constantPattern: '[A-Za-z][_A-Za-z0-9]*'
propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
privatePropertyPattern: '(_)?[A-Za-z][_A-Za-z0-9]*'
PackageNaming:
active: true
excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt'
excludes: ['**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt']
packagePattern: '^[a-z]+(\.[a-z][A-Za-z0-9]*)*$'
TopLevelPropertyNaming:
active: true
excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt'
excludes: ['**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt']
constantPattern: '[A-Z][_A-Z0-9]*'
propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
privatePropertyPattern: '_?[A-Za-z][_A-Za-z0-9]*'
VariableMaxLength:
active: true
excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt'
excludes: ['**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt']
maximumVariableNameLength: 64
VariableMinLength:
active: false
VariableNaming:
active: true
excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt'
excludes: ['**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt']
variablePattern: '[a-z][A-Za-z0-9]*'
privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*'
excludeClassPattern: '$^'
......@@ -337,10 +343,10 @@ performance:
active: true
ForEachOnRange:
active: true
excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt'
excludes: ['**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt']
SpreadOperator:
active: true
excludes: '**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt'
excludes: ['**/test/**', '**/androidTest/**', '**/*.Test.kt', '**/*.Spec.kt', '**/*.Spek.kt']
UnnecessaryTemporaryInstantiation:
active: true
......@@ -376,6 +382,10 @@ potential-bugs:
active: true
UnconditionalJumpStatementInLoop:
active: true
UnnecessaryNotNullOperator:
active: false
UnnecessarySafeCall:
active: false
UnreachableCode:
active: true
UnsafeCallOnNullableType:
......@@ -408,7 +418,7 @@ style:
includeLineWrapping: false
ForbiddenComment:
active: true
values: 'TODO:,FIXME:,STOPSHIP:'
values: ['TODO:', 'FIXME:', 'STOPSHIP:']
allowedPatterns: ''
ForbiddenImport:
active: true
......@@ -416,10 +426,10 @@ style:
forbiddenPatterns: ''
ForbiddenMethodCall:
active: true
methods: ''
methods: []
ForbiddenPublicDataClass:
active: true
ignorePackages: '*.internal,*.internal.*'
ignorePackages: ['*.internal', '*.internal.*']
ForbiddenVoid:
active: true
ignoreOverridden: true
......@@ -428,7 +438,7 @@ style:
active: true
ignoreOverridableFunction: true
excludedFunctions: 'describeContents'
excludeAnnotatedFunction: 'dagger.Provides'
excludeAnnotatedFunction: ['dagger.Provides']
LibraryCodeMustSpecifyReturnType:
active: true
LoopWithTooManyJumpStatements:
......
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
......@@ -82,6 +82,7 @@ esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
......@@ -129,6 +130,7 @@ fi
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
......
......@@ -84,6 +84,7 @@ set CMD_LINE_ARGS=%*
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
......
......@@ -10,17 +10,16 @@ plugins {
setupApp()
android.defaultConfig.applicationId = "com.github.shadowsocks"
androidExtensions.isExperimental = true
dependencies {
implementation("androidx.browser:browser:1.2.0")
implementation("androidx.constraintlayout:constraintlayout:2.0.0-beta4")
implementation("androidx.constraintlayout:constraintlayout:2.0.0-beta6")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion")
implementation("com.google.android.gms:play-services-vision:20.0.0")
implementation("com.google.android.gms:play-services-vision:20.1.0")
implementation("com.google.firebase:firebase-ads:19.1.0")
implementation("com.google.zxing:core:3.4.0")
implementation("com.takisoft.preferencex:preferencex-simplemenu:1.1.0")
implementation("com.twofortyfouram:android-plugin-api-for-locale:1.0.4")
implementation("me.zhanghai.android.fastscroll:library:1.1.2")
implementation("me.zhanghai.android.fastscroll:library:1.1.4")
implementation("xyz.belvi.mobilevision:barcodescanner:2.0.3")
}
......@@ -24,7 +24,7 @@ import android.app.Application
import android.content.res.Configuration
import androidx.appcompat.app.AppCompatDelegate
class App : Application() {
class App : Application(), androidx.work.Configuration.Provider by Core {
override fun onCreate() {
super.onCreate()
Core.init(this, MainActivity::class)
......
......@@ -88,7 +88,6 @@ class ProfileConfigFragment : PreferenceFragmentCompat(),
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
findPreference<Preference>(Key.ipv6)!!.isEnabled = serviceMode == Key.modeVpn
isProxyApps = findPreference(Key.proxyApps)!!
isProxyApps.isEnabled = serviceMode == Key.modeVpn
......@@ -100,7 +99,6 @@ class ProfileConfigFragment : PreferenceFragmentCompat(),
findPreference<Preference>(Key.metered)!!.apply {
if (Build.VERSION.SDK_INT >= 28) isEnabled = serviceMode == Key.modeVpn else remove()
}
findPreference<Preference>(Key.udpdns)!!.isEnabled = serviceMode != Key.modeProxy
plugin = findPreference(Key.plugin)!!
pluginConfigure = findPreference(Key.pluginConfigure)!!
pluginConfigure.setOnBindEditTextListener(EditTextPreferenceModifiers.Monospace)
......
......@@ -502,7 +502,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
try {
val profiles = Profile.findAllUrls(
Core.clipboard.primaryClip!!.getItemAt(0).text,
Core.currentProfile?.first
Core.currentProfile?.main
).toList()
if (profiles.isNotEmpty()) {
profiles.forEach { ProfileManager.createProfile(it) }
......@@ -533,7 +533,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
}
R.id.action_manual_settings -> {
startConfig(ProfileManager.createProfile(
Profile().also { Core.currentProfile?.first?.copyFeatureSettingsTo(it) }))
Profile().also { Core.currentProfile?.main?.copyFeatureSettingsTo(it) }))
true
}
R.id.action_export_clipboard -> {
......
......@@ -105,7 +105,7 @@ class ScannerActivity : AppCompatActivity(), BarcodeRetriever {
}
override fun onRetrieved(barcode: Barcode) = runOnUiThread {
Profile.findAllUrls(barcode.rawValue, Core.currentProfile?.first).forEach { ProfileManager.createProfile(it) }
Profile.findAllUrls(barcode.rawValue, Core.currentProfile?.main).forEach { ProfileManager.createProfile(it) }
onSupportNavigateUp()
}
override fun onRetrievedMultiple(closetToClick: Barcode?, barcode: MutableList<BarcodeGraphic>?) = check(false)
......@@ -141,7 +141,7 @@ class ScannerActivity : AppCompatActivity(), BarcodeRetriever {
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
REQUEST_IMPORT, REQUEST_IMPORT_OR_FINISH -> if (resultCode == Activity.RESULT_OK) {
val feature = Core.currentProfile?.first
val feature = Core.currentProfile?.main
try {
var success = false
data!!.datas.forEachTry { uri ->
......
......@@ -362,7 +362,7 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener,
}
private val isEnabled get() = (activity as? MainActivity)?.state == BaseService.State.Stopped ||
Core.currentProfile?.first?.route != Acl.CUSTOM_RULES
Core.currentProfile?.main?.route != Acl.CUSTOM_RULES
private val selectedItems = HashSet<Any>()
private val adapter by lazy { AclRulesAdapter() }
......
......@@ -14,8 +14,6 @@ android {
}
}
androidExtensions.isExperimental = true
mavenPublish.targets.getByName("uploadArchives") {
releaseRepositoryUrl = "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
snapshotRepositoryUrl = "https://oss.sonatype.org/content/repositories/snapshots/"
......@@ -27,6 +25,6 @@ dependencies {
api(kotlin("stdlib-jdk8", rootProject.extra.get("kotlinVersion").toString()))
api("androidx.core:core-ktx:1.2.0")
// https://android-developers.googleblog.com/2019/07/android-q-beta-5-update.html
api("androidx.drawerlayout:drawerlayout:1.1.0-beta01")
api("androidx.drawerlayout:drawerlayout:1.1.0-rc01")
api("com.google.android.material:material:1.1.0")
}
rootProject.extra.apply {
set("androidPlugin", "com.android.tools.build:gradle:4.1.0-alpha06")
set("androidPlugin", "com.android.tools.build:gradle:4.1.0-beta01")
set("kotlinVersion", "1.3.72")
}
......
......@@ -24,7 +24,7 @@ import android.app.Application
import android.content.res.Configuration
import com.github.shadowsocks.Core
class App : Application() {
class App : Application(), androidx.work.Configuration.Provider by Core {
override fun onCreate() {
super.onCreate()
Core.init(this, MainActivity::class)
......
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