diff --git a/.github/faq.md b/.github/faq.md index f7e973e015..58af541a0b 100644 --- a/.github/faq.md +++ b/.github/faq.md @@ -31,9 +31,10 @@ The exclamation mark in the Wi-Fi/cellular icon appears because the system fails 1. Some ROM has broken VPNService implementation, especially for IPv6; 2. Some ROM has aggressive (or called broken) background service killing policy; -3. If you have Xposed framework and/or battery saver apps, it's likely that this app wouldn't work well with these either. +3. Some ROM like [Flyme](https://github.com/shadowsocks/shadowsocks-android/issues/1589) has **very** broken direct boot support; +4. If you have Xposed framework and/or battery saver apps, it's likely that this app wouldn't work well with these either. -* Fixes for MIUI: [#772](https://github.com/shadowsocks/shadowsocks-android/issues/772) +* Fixes for MIUI: [#772](https://github.com/shadowsocks/shadowsocks-android/issues/772) [#888](https://github.com/shadowsocks/shadowsocks-android/issues/888) * Fixes for Huawei: [#1091 (comment)](https://github.com/shadowsocks/shadowsocks-android/issues/1091#issuecomment-276949836) * Related to Xposed: [#1414](https://github.com/shadowsocks/shadowsocks-android/issues/1414) * Samsung and/or Brevent: [#1410](https://github.com/shadowsocks/shadowsocks-android/issues/1410) diff --git a/.gitignore b/.gitignore index 678f33aafd..e22a1d6350 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,6 @@ core/src/overture/src/gopkg.in # work in progress tv/ things/ + +# release apks +*.apk diff --git a/README.md b/README.md index cac7431ab9..3bcf2be270 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ ## Shadowsocks for Android [![Build Status](https://api.travis-ci.org/shadowsocks/shadowsocks-android.svg)](https://travis-ci.org/shadowsocks/shadowsocks-android) +[![API](https://img.shields.io/badge/API-21%2B-brightgreen.svg?style=flat)](https://android-arsenal.com/api?level=21) [![Releases](https://img.shields.io/github/downloads/shadowsocks/shadowsocks-android/total.svg)](https://github.com/shadowsocks/shadowsocks-android/releases) +[![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) A [shadowsocks](http://shadowsocks.org) client for Android, written in Kotlin. diff --git a/build.gradle b/build.gradle index 92e6c03e29..188b2bea1c 100644 --- a/build.gradle +++ b/build.gradle @@ -1,9 +1,11 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. +apply plugin: 'com.github.ben-manes.versions' + buildscript { ext { - kotlinVersion = '1.2.10' - minSdkVersion = 19 + kotlinVersion = '1.2.21' + minSdkVersion = 21 sdkVersion = 27 buildToolsVersion = '27.0.3' supportLibraryVersion = '27.0.2' @@ -21,7 +23,8 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:3.0.1' - classpath 'com.google.gms:google-services:3.1.1' + classpath 'com.github.ben-manes:gradle-versions-plugin:0.17.0' + classpath 'com.google.gms:google-services:3.2.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" } } diff --git a/core/build.gradle b/core/build.gradle index 2ebe255a8f..434df1e05e 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -36,19 +36,28 @@ android { } task goBuild(type: Exec) { - executable "sh" - args "-c", "src/overture/make.bash " + minSdkVersion + if (Os.isFamily(Os.FAMILY_WINDOWS)) { + executable "cmd.exe" + args "/c", "src\\overture\\make.bat " + minSdkVersion + } else { + executable "sh" + args "-c", "src/overture/make.bash " + minSdkVersion + } } task goClean(type: Exec) { - executable "sh" - args "-c", "src/overture/clean.bash" + if (Os.isFamily(Os.FAMILY_WINDOWS)) { + executable "cmd.exe" + args "/c", "src\\overture\\clean.bat" + } else { + executable "sh" + args "-c", "src/overture/clean.bash" + } } tasks.whenTaskAdded { task -> if ((task.name == 'generateJsonModelDebug' || - task.name == 'generateJsonModelRelease') && - !Os.isFamily(Os.FAMILY_WINDOWS)) { + task.name == 'generateJsonModelRelease')) { task.dependsOn(goBuild) } } diff --git a/core/src/main/java/com/github/shadowsocks/JniHelper.java b/core/src/main/java/com/github/shadowsocks/JniHelper.java index ff111a6515..d4167a67b7 100755 --- a/core/src/main/java/com/github/shadowsocks/JniHelper.java +++ b/core/src/main/java/com/github/shadowsocks/JniHelper.java @@ -43,8 +43,6 @@ import android.support.annotation.Nullable; import android.system.ErrnoException; -import java.net.InetAddress; - public class JniHelper { static { System.loadLibrary("jni-helper"); @@ -54,8 +52,7 @@ public class JniHelper { public static void sigtermCompat(@NonNull Process process) throws Exception { if (Build.VERSION.SDK_INT >= 24) throw new UnsupportedOperationException("Never call this method in OpenJDK!"); int errno = sigterm(process); - if (errno != 0) throw Build.VERSION.SDK_INT >= 21 - ? new ErrnoException("kill", errno) : new Exception("kill failed: " + errno); + if (errno != 0) throw new ErrnoException("kill", errno); } @Deprecated // only implemented for before API 24 diff --git a/core/src/main/jni/Application.mk b/core/src/main/jni/Application.mk index 1935e6469e..15c1eec07d 100644 --- a/core/src/main/jni/Application.mk +++ b/core/src/main/jni/Application.mk @@ -1,4 +1,4 @@ APP_ABI := armeabi-v7a arm64-v8a x86 -APP_PLATFORM := android-19 +APP_PLATFORM := android-21 APP_STL := c++_static NDK_TOOLCHAIN_VERSION := clang diff --git a/core/src/main/jni/build-shared-executable.mk b/core/src/main/jni/build-shared-executable.mk index 877480955e..05239df186 100644 --- a/core/src/main/jni/build-shared-executable.mk +++ b/core/src/main/jni/build-shared-executable.mk @@ -16,8 +16,8 @@ # executable program # # Modified by @Mygod, based on: -# https://android.googlesource.com/platform/ndk/+/f2e98f8c066aed59caf61163d4b87c2b858f9814/build/core/build-shared-library.mk -# https://android.googlesource.com/platform/ndk/+/f2e98f8c066aed59caf61163d4b87c2b858f9814/build/core/build-executable.mk +# https://android.googlesource.com/platform/ndk/+/a355a4e/build/core/build-shared-library.mk +# https://android.googlesource.com/platform/ndk/+/a355a4e/build/core/build-executable.mk LOCAL_BUILD_SCRIPT := BUILD_EXECUTABLE LOCAL_MAKEFILE := $(local-makefile) $(call check-defined-LOCAL_MODULE,$(LOCAL_BUILD_SCRIPT)) diff --git a/core/src/main/jni/shadowsocks-libev b/core/src/main/jni/shadowsocks-libev index afce1b3f42..6040be9d0a 160000 --- a/core/src/main/jni/shadowsocks-libev +++ b/core/src/main/jni/shadowsocks-libev @@ -1 +1 @@ -Subproject commit afce1b3f426b2c8a746fd8e7b19973e8c15ec327 +Subproject commit 6040be9d0af812382934defe317c2e28434494ad diff --git a/core/src/overture/clean.bat b/core/src/overture/clean.bat new file mode 100644 index 0000000000..be18450130 --- /dev/null +++ b/core/src/overture/clean.bat @@ -0,0 +1,10 @@ +@ECHO OFF + +SET DIR=%~dp0 +SET DEPS=%DIR%\.deps + +RD /S /Q %DEPS% +RD /S /Q %DIR%\go\bin +RD /S /Q %DIR%\bin + +ECHO "Successfully clean overture" diff --git a/core/src/overture/make.bash b/core/src/overture/make.bash index a857f2c518..0aadf24e41 100755 --- a/core/src/overture/make.bash +++ b/core/src/overture/make.bash @@ -12,7 +12,7 @@ TARGET=$DIR/bin DEPS=$DIR/.deps ANDROID_ARM_TOOLCHAIN=$DEPS/android-toolchain-${MIN_API}-arm -ANDROID_ARM64_TOOLCHAIN=$DEPS/android-toolchain-21-arm64 +ANDROID_ARM64_TOOLCHAIN=$DEPS/android-toolchain-${MIN_API}-arm64 ANDROID_X86_TOOLCHAIN=$DEPS/android-toolchain-${MIN_API}-x86 ANDROID_ARM_CC=$ANDROID_ARM_TOOLCHAIN/bin/arm-linux-androideabi-clang @@ -35,7 +35,7 @@ fi if [ ! -f "$ANDROID_ARM64_CC" ]; then echo "Make standalone toolchain for ARM64 arch" $ANDROID_NDK_HOME/build/tools/make_standalone_toolchain.py --arch arm64 \ - --api 21 --install-dir $ANDROID_ARM64_TOOLCHAIN + --api $MIN_API --install-dir $ANDROID_ARM64_TOOLCHAIN fi if [ ! -f "$ANDROID_X86_CC" ]; then diff --git a/core/src/overture/make.bat b/core/src/overture/make.bat new file mode 100644 index 0000000000..fa23ba8b07 --- /dev/null +++ b/core/src/overture/make.bat @@ -0,0 +1,121 @@ +@ECHO OFF +SETLOCAL + +IF NOT DEFINED ANDROID_NDK_HOME ( + SET ANDROID_NDK_HOME=%ANDROID_HOME%\ndk-bundle +) + +SET DIR=%~dp0 +SET MIN_API=%1% +SET TARGET=%DIR%\bin +SET DEPS=%DIR%\.deps + +SET ANDROID_ARM_TOOLCHAIN=%DEPS%\android-toolchain-%MIN_API%-arm +SET ANDROID_ARM64_TOOLCHAIN=%DEPS%\android-toolchain-%MIN_API%-arm64 +SET ANDROID_X86_TOOLCHAIN=%DEPS%\android-toolchain-%MIN_API%-x86 + +SET ANDROID_ARM_CC=%ANDROID_ARM_TOOLCHAIN%\bin\arm-linux-androideabi-clang +SET ANDROID_ARM_STRIP=%ANDROID_ARM_TOOLCHAIN%\bin\arm-linux-androideabi-strip + +SET ANDROID_ARM64_CC=%ANDROID_ARM64_TOOLCHAIN%\bin\aarch64-linux-android-clang +SET ANDROID_ARM64_STRIP=%ANDROID_ARM64_TOOLCHAIN%\bin\aarch64-linux-android-strip + +SET ANDROID_X86_CC=%ANDROID_X86_TOOLCHAIN%\bin\i686-linux-android-clang +SET ANDROID_X86_STRIP=%ANDROID_X86_TOOLCHAIN%\bin\i686-linux-android-strip + +MKDIR %DEPS%>nul 2>nul +MKDIR %TARGET%\armeabi-v7a>nul 2>nul +MKDIR %TARGET%\x86>nul 2>nul +MKDIR %TARGET%\arm64-v8a>nul 2>nul + +IF NOT EXIST %ANDROID_ARM_CC% ( + ECHO "Make standalone toolchain for ARM arch" + %ANDROID_NDK_HOME%\build\tools\make_standalone_toolchain.py --arch arm ^ + --api %MIN_API% --install-dir %ANDROID_ARM_TOOLCHAIN% +) + +IF NOT EXIST %ANDROID_ARM64_CC% ( + ECHO "Make standalone toolchain for ARM64 arch" + %ANDROID_NDK_HOME%\build\tools\make_standalone_toolchain.py --arch arm64 ^ + --api %MIN_API% --install-dir %ANDROID_ARM64_TOOLCHAIN% +) + +IF NOT EXIST %ANDROID_X86_CC% ( + ECHO "Make standalone toolchain for X86 arch" + %ANDROID_NDK_HOME%\build\tools\make_standalone_toolchain.py --arch x86 ^ + --api %MIN_API% --install-dir %ANDROID_X86_TOOLCHAIN% +) + +IF NOT EXIST %DIR%\go\bin\go.exe ( + ECHO "Build the custom go" + + PUSHD %DIR%\go\src + CALL make.bat + POPD +) + +SET GOROOT=%DIR%\go +SET GOPATH=%DIR% +SET PATH=%GOROOT%\bin;%GOPATH%\bin;%PATH% + +SET BUILD=1 +IF EXIST "%TARGET%\armeabi-v7a\liboverture.so" ( + IF EXIST "%TARGET%\arm64-v8a\liboverture.so" ( + IF EXIST "%TARGET%\x86\liboverture.so" ( + SET BUILD=0 + ) + ) +) + +IF %BUILD% == 1 ( + ECHO "Get dependences for overture" + go.exe get -u github.com\tools\godep + + PUSHD %GOPATH%\src\github.com\shadowsocks\overture\main + godep.exe restore + + ECHO "Cross compile overture for arm" + IF NOT EXIST "%TARGET%\armeabi-v7a\liboverture.so" ( + SETLOCAL + SET CGO_ENABLED=1 + SET CC=%ANDROID_ARM_CC% + SET GOOS=android + SET GOARCH=arm + SET GOARM=7 + go.exe build -ldflags="-s -w" + %ANDROID_ARM_STRIP% main + MOVE main %TARGET%\armeabi-v7a\liboverture.so>nul 2>nul + ENDLOCAL + ) + + ECHO "Cross compile overture for arm64" + IF NOT EXIST "%TARGET%\arm64-v8a\liboverture.so" ( + SETLOCAL + SET CGO_ENABLED=1 + SET CC=%ANDROID_ARM64_CC% + SET GOOS=android + SET GOARCH=arm64 + go.exe build -ldflags="-s -w" + %ANDROID_ARM64_STRIP% main + MOVE main %TARGET%\arm64-v8a\liboverture.so>nul 2>nul + ENDLOCAL + ) + + ECHO "Cross compile overture for x86" + IF NOT EXIST "%TARGET%\x86\liboverture.so" ( + SETLOCAL + SET CGO_ENABLED=1 + SET CC=%ANDROID_X86_CC% + SET GOOS=android + SET GOARCH=386 + go.exe build -ldflags="-s -w" + %ANDROID_X86_STRIP% main + MOVE main %TARGET%\x86\liboverture.so>nul 2>nul + ENDLOCAL + ) + + POPD +) + +ECHO "Successfully build overture" +ENDLOCAL \ No newline at end of file diff --git a/core/src/overture/src/github.com/shadowsocks/overture b/core/src/overture/src/github.com/shadowsocks/overture index fae912215d..466f9902c9 160000 --- a/core/src/overture/src/github.com/shadowsocks/overture +++ b/core/src/overture/src/github.com/shadowsocks/overture @@ -1 +1 @@ -Subproject commit fae912215d944d9536fb8445803c430219ee8218 +Subproject commit 466f9902c9779b8801f521f88db1d1522c880c26 diff --git a/mobile/build.gradle b/mobile/build.gradle index 222b44dced..2a2cf5e7a2 100644 --- a/mobile/build.gradle +++ b/mobile/build.gradle @@ -1,3 +1,4 @@ +import com.android.build.OutputFile import java.util.regex.Matcher import java.util.regex.Pattern @@ -7,7 +8,7 @@ apply plugin: 'kotlin-android' def getCurrentFlavor() { String task = getGradle().getStartParameter().getTaskRequests().toString() Matcher matcher = Pattern.compile("(assemble|generate)\\w*(Release|Debug)").matcher(task) - if (matcher.find()) return matcher.group(1).toLowerCase() else { + if (matcher.find()) return matcher.group(2).toLowerCase() else { println "Warning: No match found for $task" return "debug" } @@ -20,11 +21,10 @@ android { applicationId "com.github.shadowsocks" minSdkVersion rootProject.minSdkVersion targetSdkVersion rootProject.sdkVersion - versionCode 204 - versionName "4.4.4" + versionCode 4040600 + versionName "4.4.6" testApplicationId "com.github.shadowsocks.test" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" - vectorDrawables.useSupportLibrary true resConfigs "fa", "fr", "ja", "ko", "ru", "zh-rCN", "zh-rTW" } buildTypes { @@ -37,6 +37,15 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } + lintOptions.checkReleaseBuilds false + splits { + abi { + enable true + reset() + include 'armeabi-v7a', 'arm64-v8a', 'x86' + universalApk true + } + } sourceSets.main.jniLibs.srcDirs += new File(project(':core').buildDir, "intermediates/bundles/${getCurrentFlavor()}/jni") sourceSets.main.jniLibs.srcDirs += new File(project(':core').projectDir, "src/overture/bin") @@ -48,17 +57,13 @@ dependencies { implementation "com.android.support:design:$supportLibraryVersion" implementation "com.android.support:gridlayout-v7:$supportLibraryVersion" implementation 'com.futuremind.recyclerfastscroll:fastscroll:0.2.5' - implementation 'com.evernote:android-job:1.2.1' + implementation 'com.evernote:android-job:1.2.2' implementation "com.google.android.gms:play-services-ads:$playServicesVersion" implementation "com.google.android.gms:play-services-analytics:$playServicesVersion" - implementation "com.google.android.gms:play-services-gcm:$playServicesVersion" implementation "com.google.firebase:firebase-config:$playServicesVersion" implementation 'com.j256.ormlite:ormlite-android:5.0' - implementation 'com.mikepenz:crossfader:1.5.1' - implementation 'com.mikepenz:fastadapter:3.0.4' - implementation 'com.mikepenz:iconics-core:3.0.0' - implementation 'com.mikepenz:materialdrawer:6.0.2' - implementation 'com.mikepenz:materialize:1.1.2' + implementation 'com.mikepenz:crossfader:1.5.2' + implementation 'com.mikepenz:materialdrawer:6.0.3' implementation 'com.squareup.okhttp3:okhttp:3.9.1' implementation "com.takisoft.fix:preference-v7-simplemenu:$takisoftFixVersion" implementation 'com.twofortyfouram:android-plugin-api-for-locale:1.0.2' @@ -76,3 +81,11 @@ repositories { } apply plugin: 'com.google.gms.google-services' + +ext.abiCodes = ['armeabi-v7a': 1, 'arm64-v8a': 2, x86: 3] +if (getCurrentFlavor() == 'release') android.applicationVariants.all { variant -> + variant.outputs.each { output -> + def offset = project.ext.abiCodes.get(output.getFilter(OutputFile.ABI)) + if (offset != null) output.versionCodeOverride = variant.versionCode + offset + } +} diff --git a/mobile/src/main/AndroidManifest.xml b/mobile/src/main/AndroidManifest.xml index a552d5b1a6..fc8517ce32 100644 --- a/mobile/src/main/AndroidManifest.xml +++ b/mobile/src/main/AndroidManifest.xml @@ -171,9 +171,11 @@ - + diff --git a/mobile/src/main/java/com/github/shadowsocks/App.kt b/mobile/src/main/java/com/github/shadowsocks/App.kt index a59306f583..c2e9083298 100644 --- a/mobile/src/main/java/com/github/shadowsocks/App.kt +++ b/mobile/src/main/java/com/github/shadowsocks/App.kt @@ -32,7 +32,6 @@ import android.content.pm.PackageManager import android.content.res.Configuration import android.os.Build import android.os.Handler -import android.os.LocaleList import android.os.Looper import android.support.annotation.RequiresApi import android.support.v4.os.UserManagerCompat @@ -59,20 +58,11 @@ import com.j256.ormlite.logger.LocalLog import com.takisoft.fix.support.v7.preference.PreferenceFragmentCompat import java.io.File import java.io.IOException -import java.util.* class App : Application() { companion object { lateinit var app: App private const val TAG = "ShadowsocksApplication" - - // The ones in Locale doesn't have script included - private val SIMPLIFIED_CHINESE by lazy { - if (Build.VERSION.SDK_INT >= 21) Locale.forLanguageTag("zh-Hans-CN") else Locale.SIMPLIFIED_CHINESE - } - private val TRADITIONAL_CHINESE by lazy { - if (Build.VERSION.SDK_INT >= 21) Locale.forLanguageTag("zh-Hant-TW") else Locale.TRADITIONAL_CHINESE - } } val handler by lazy { Handler(Looper.getMainLooper()) } @@ -112,59 +102,11 @@ class App : Application() { t.printStackTrace() } - private fun checkChineseLocale(config: Configuration) { - fun check(locale: Locale): Locale? { - if (locale.language != "zh") return null - when (locale.country) { "CN", "TW" -> return null } - if (Build.VERSION.SDK_INT >= 21) when (locale.script) { - "Hans" -> return SIMPLIFIED_CHINESE - "Hant" -> return TRADITIONAL_CHINESE - else -> Log.w(TAG, "Unknown zh locale script: ${locale.script}. Falling back to trying countries...") - } - when (locale.country) { - "SG" -> return SIMPLIFIED_CHINESE - "HK", "MO" -> return TRADITIONAL_CHINESE - } - Log.w(TAG, "Unknown zh locale: %s. Falling back to zh-Hans-CN..." - .format(Locale.ENGLISH, if (Build.VERSION.SDK_INT >= 21) locale.toLanguageTag() else locale)) - return SIMPLIFIED_CHINESE - } - if (Build.VERSION.SDK_INT >= 24) @RequiresApi(24) { - val localeList = config.locales - var changed = false - val newList = Array(localeList.size(), { i -> - val locale = localeList[i] - val newLocale = check(locale) - if (newLocale == null) locale else { - changed = true - newLocale - } - }) - if (changed) { - val newConfig = Configuration(config) - newConfig.locales = LocaleList(*(newList.distinct().toTypedArray())) - val res = resources - res.updateConfiguration(newConfig, res.displayMetrics) - } - } else { - @Suppress("DEPRECATION") - val newLocale = check(config.locale) - if (newLocale != null) { - val newConfig = Configuration(config) - @Suppress("DEPRECATION") - newConfig.locale = newLocale - val res = resources - res.updateConfiguration(newConfig, res.displayMetrics) - } - } - } - override fun onCreate() { super.onCreate() app = this if (!BuildConfig.DEBUG) System.setProperty(LocalLog.LOCAL_LOG_LEVEL_PROPERTY, "ERROR") AppCompatDelegate.setCompatVectorFromResourcesEnabled(true) - checkChineseLocale(resources.configuration) PreferenceFragmentCompat.registerPreferenceFragment(IconListPreference::class.java, BottomSheetPreferenceDialogFragment::class.java) @@ -203,6 +145,15 @@ class App : Application() { DataStore.publicStore.putLong(Key.assetUpdateTime, info.lastUpdateTime) } + updateNotificationChannels() + } + + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + updateNotificationChannels() + } + + private fun updateNotificationChannels() { if (Build.VERSION.SDK_INT >= 26) @RequiresApi(26) { val nm = getSystemService(NotificationManager::class.java) nm.createNotificationChannels(listOf( @@ -216,11 +167,6 @@ class App : Application() { } } - override fun onConfigurationChanged(newConfig: Configuration) { - super.onConfigurationChanged(newConfig) - checkChineseLocale(newConfig) - } - fun listenForPackageChanges(callback: () -> Unit): BroadcastReceiver { val filter = IntentFilter(Intent.ACTION_PACKAGE_ADDED) filter.addAction(Intent.ACTION_PACKAGE_REMOVED) diff --git a/mobile/src/main/java/com/github/shadowsocks/MainActivity.kt b/mobile/src/main/java/com/github/shadowsocks/MainActivity.kt index d64123b4dc..34506e4b44 100644 --- a/mobile/src/main/java/com/github/shadowsocks/MainActivity.kt +++ b/mobile/src/main/java/com/github/shadowsocks/MainActivity.kt @@ -21,8 +21,10 @@ package com.github.shadowsocks import android.app.Activity +import android.app.PendingIntent import android.app.backup.BackupManager import android.content.ActivityNotFoundException +import android.content.Context import android.content.Intent import android.net.Uri import android.net.VpnService @@ -56,11 +58,8 @@ import com.github.shadowsocks.utils.Key import com.github.shadowsocks.utils.responseLength import com.github.shadowsocks.utils.thread import com.github.shadowsocks.widget.ServiceButton -import com.mikepenz.crossfader.Crossfader -import com.mikepenz.crossfader.view.CrossFadeSlidingPaneLayout import com.mikepenz.materialdrawer.Drawer import com.mikepenz.materialdrawer.DrawerBuilder -import com.mikepenz.materialdrawer.interfaces.ICrossfader import com.mikepenz.materialdrawer.model.PrimaryDrawerItem import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem import java.net.HttpURLConnection @@ -81,12 +80,14 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, Drawe private const val DRAWER_FAQ = 4L private const val DRAWER_CUSTOM_RULES = 5L + fun pendingIntent(context: Context) = PendingIntent.getActivity(context, 0, + Intent(context, MainActivity::class.java).setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT), 0) + var stateListener: ((Int) -> Unit)? = null } // UI private lateinit var fab: ServiceButton - internal var crossfader: Crossfader? = null internal lateinit var drawer: Drawer private var previousSelectedDrawer: Long = 0 // it's actually lateinit @@ -168,9 +169,8 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, Drawe url.openConnection(Proxy(Proxy.Type.SOCKS, InetSocketAddress("127.0.0.1", DataStore.portProxy)))) as HttpURLConnection + conn.setRequestProperty("Connection", "close") conn.instanceFollowRedirects = false - conn.connectTimeout = 10000 - conn.readTimeout = 10000 conn.useCaches = false val (success, result) = try { val start = SystemClock.elapsedRealtime() @@ -213,7 +213,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, Drawe override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.layout_main) - val drawerBuilder = DrawerBuilder() + drawer = DrawerBuilder() .withActivity(this) .withTranslucentStatusBar(true) .withHeader(R.layout.layout_header) @@ -250,28 +250,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, Drawe .withOnDrawerItemClickListener(this) .withActionBarDrawerToggle(true) .withSavedInstance(savedInstanceState) - val miniDrawerWidth = resources.getDimension(R.dimen.material_mini_drawer_item) - if (resources.displayMetrics.widthPixels >= - resources.getDimension(R.dimen.profile_item_max_width) + miniDrawerWidth) { - drawer = drawerBuilder.withGenerateMiniDrawer(true).buildView() - val crossfader = Crossfader() - this.crossfader = crossfader - crossfader - .withContent(findViewById(android.R.id.content)) - .withFirst(drawer.slider, resources.getDimensionPixelSize(R.dimen.material_drawer_width)) - .withSecond(drawer.miniDrawer.build(this), miniDrawerWidth.toInt()) - .withSavedInstance(savedInstanceState) - .build() - if (resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL) - crossfader.crossFadeSlidingPaneLayout.setShadowDrawableRight( - AppCompatResources.getDrawable(this, R.drawable.material_drawer_shadow_right)) - else crossfader.crossFadeSlidingPaneLayout.setShadowDrawableLeft( - AppCompatResources.getDrawable(this, R.drawable.material_drawer_shadow_left)) - drawer.miniDrawer.withCrossFader(object : ICrossfader { // a wrapper is needed - override fun isCrossfaded(): Boolean = crossfader.isCrossFaded - override fun crossfade() = crossfader.crossFade() - }) - } else drawer = drawerBuilder.build() + .build() if (savedInstanceState == null) displayFragment(ProfilesFragment()) previousSelectedDrawer = drawer.currentSelection @@ -395,7 +374,6 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, Drawe override fun onSaveInstanceState(outState: Bundle?) { super.onSaveInstanceState(outState) drawer.saveInstanceState(outState) - crossfader?.saveInstanceState(outState) } override fun onDestroy() { diff --git a/mobile/src/main/java/com/github/shadowsocks/ProfileConfigFragment.kt b/mobile/src/main/java/com/github/shadowsocks/ProfileConfigFragment.kt index ea511abe4e..988179517d 100644 --- a/mobile/src/main/java/com/github/shadowsocks/ProfileConfigFragment.kt +++ b/mobile/src/main/java/com/github/shadowsocks/ProfileConfigFragment.kt @@ -80,13 +80,11 @@ class ProfileConfigFragment : PreferenceFragmentCompatDividers(), Toolbar.OnMenu val serviceMode = DataStore.serviceMode findPreference(Key.remoteDns).isEnabled = serviceMode != Key.modeProxy isProxyApps = findPreference(Key.proxyApps) as SwitchPreference - if (Build.VERSION.SDK_INT < 21) isProxyApps.parent!!.removePreference(isProxyApps) else { - isProxyApps.isEnabled = serviceMode == Key.modeVpn - isProxyApps.setOnPreferenceClickListener { - startActivity(Intent(activity, AppManager::class.java)) - isProxyApps.isChecked = true - false - } + isProxyApps.isEnabled = serviceMode == Key.modeVpn + isProxyApps.setOnPreferenceClickListener { + startActivity(Intent(activity, AppManager::class.java)) + isProxyApps.isChecked = true + false } findPreference(Key.udpdns).isEnabled = serviceMode != Key.modeProxy plugin = findPreference(Key.plugin) as IconListPreference diff --git a/mobile/src/main/java/com/github/shadowsocks/ToolbarFragment.kt b/mobile/src/main/java/com/github/shadowsocks/ToolbarFragment.kt index 9238d77621..bb49aa17c7 100644 --- a/mobile/src/main/java/com/github/shadowsocks/ToolbarFragment.kt +++ b/mobile/src/main/java/com/github/shadowsocks/ToolbarFragment.kt @@ -35,7 +35,7 @@ open class ToolbarFragment : Fragment() { super.onViewCreated(view, savedInstanceState) toolbar = view.findViewById(R.id.toolbar) val activity = activity as MainActivity - if (activity.crossfader == null) activity.drawer.setToolbar(activity, toolbar, true) + activity.drawer.setToolbar(activity, toolbar, true) } open fun onTrafficUpdated(profileId: Int, txRate: Long, rxRate: Long, txTotal: Long, rxTotal: Long) { } diff --git a/mobile/src/main/java/com/github/shadowsocks/acl/CustomRulesFragment.kt b/mobile/src/main/java/com/github/shadowsocks/acl/CustomRulesFragment.kt index 64c511ced0..c4dd8101cd 100644 --- a/mobile/src/main/java/com/github/shadowsocks/acl/CustomRulesFragment.kt +++ b/mobile/src/main/java/com/github/shadowsocks/acl/CustomRulesFragment.kt @@ -20,21 +20,21 @@ package com.github.shadowsocks.acl +import android.annotation.TargetApi import android.content.ClipData import android.content.ClipboardManager import android.content.Context +import android.os.Build import android.os.Bundle import android.support.design.widget.Snackbar +import android.support.v4.content.ContextCompat import android.support.v7.app.AlertDialog import android.support.v7.widget.DefaultItemAnimator import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.RecyclerView import android.support.v7.widget.Toolbar import android.support.v7.widget.helper.ItemTouchHelper -import android.view.LayoutInflater -import android.view.MenuItem -import android.view.View -import android.view.ViewGroup +import android.view.* import android.widget.EditText import android.widget.Spinner import android.widget.TextView @@ -48,12 +48,13 @@ import com.github.shadowsocks.ToolbarFragment import com.github.shadowsocks.bg.BaseService import com.github.shadowsocks.utils.Subnet import com.github.shadowsocks.utils.asIterable +import com.github.shadowsocks.utils.resolveResourceId import com.github.shadowsocks.widget.UndoSnackbarManager import java.net.IDN import java.net.URL import java.util.* -class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { +class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener, ActionMode.Callback { companion object { private const val TEMPLATE_REGEX_DOMAIN = "(^|\\.)%s$" @@ -288,13 +289,12 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { private val selectedItems = HashSet() private val adapter by lazy { AclRulesAdapter() } private lateinit var list: RecyclerView - private var selectionItem: MenuItem? = null + private var mode: ActionMode? = null private lateinit var undoManager: UndoSnackbarManager private val clipboard by lazy { activity!!.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager } private fun onSelectedItemsUpdated() { - val selectionItem = selectionItem - if (selectionItem != null) selectionItem.isVisible = selectedItems.isNotEmpty() + if (selectedItems.isEmpty()) mode?.finish() else if (mode == null) mode = toolbar.startActionMode(this) } private fun createAclRuleDialog(item: Any = ""): Triple { @@ -345,9 +345,6 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { toolbar.setTitle(R.string.custom_rules) toolbar.inflateMenu(R.menu.custom_rules_menu) toolbar.setOnMenuItemClickListener(this) - val selectionItem = toolbar.menu.findItem(R.id.selection) - selectionItem.isVisible = selectedItems.isNotEmpty() - this.selectionItem = selectionItem list = view.findViewById(R.id.list) list.layoutManager = LinearLayoutManager(activity, LinearLayoutManager.VERTICAL, false) list.itemAnimator = DefaultItemAnimator() @@ -365,10 +362,9 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { } override fun onBackPressed(): Boolean { - return if (selectedItems.isNotEmpty()) { - selectedItems.clear() - onSelectedItemsUpdated() - adapter.notifyDataSetChanged() + val mode = mode + return if (mode != null) { + mode.finish() true } else super.onBackPressed() } @@ -395,24 +391,6 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { } override fun onMenuItemClick(item: MenuItem): Boolean = when (item.itemId) { - R.id.action_select_all -> { - adapter.selectAll() - true - } - R.id.action_cut -> { - copySelected() - adapter.removeSelected() - true - } - R.id.action_copy -> { - copySelected() - true - } - R.id.action_delete -> { - adapter.removeSelected() - true - } - R.id.action_manual_settings -> { val (templateSelector, editText, dialog) = createAclRuleDialog() dialog.setPositiveButton(android.R.string.ok, { _, _ -> @@ -444,4 +422,50 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { undoManager.flush() super.onDetach() } + + override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean { + val activity = activity!! + val window = activity.window + // In the end material_grey_100 is used for background, see AppCompatDrawableManager (very complicated) + if (Build.VERSION.SDK_INT >= 23) { + window.statusBarColor = ContextCompat.getColor(activity, R.color.material_grey_300) + window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR + } else window.statusBarColor = ContextCompat.getColor(activity, R.color.material_grey_600) + activity.menuInflater.inflate(R.menu.custom_rules_selection, menu) + toolbar.touchscreenBlocksFocus = true + return true + } + override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean = false + override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean = when (item.itemId) { + R.id.action_select_all -> { + adapter.selectAll() + true + } + R.id.action_cut -> { + copySelected() + adapter.removeSelected() + true + } + R.id.action_copy -> { + copySelected() + true + } + R.id.action_delete -> { + adapter.removeSelected() + true + } + else -> false + } + override fun onDestroyActionMode(mode: ActionMode) { + val activity = activity!! + val window = activity.window + window.statusBarColor = ContextCompat.getColor(activity, + activity.theme.resolveResourceId(android.R.attr.statusBarColor)) + window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE + toolbar.touchscreenBlocksFocus = false + selectedItems.clear() + onSelectedItemsUpdated() + adapter.notifyDataSetChanged() + this.mode = null + } } diff --git a/mobile/src/main/java/com/github/shadowsocks/bg/Dns.kt b/mobile/src/main/java/com/github/shadowsocks/bg/Dns.kt index 05ab6bc977..56d6840de4 100644 --- a/mobile/src/main/java/com/github/shadowsocks/bg/Dns.kt +++ b/mobile/src/main/java/com/github/shadowsocks/bg/Dns.kt @@ -20,49 +20,19 @@ package com.github.shadowsocks.bg -import android.content.Context -import android.net.ConnectivityManager -import android.net.LinkProperties -import android.os.Build import android.util.Log import com.github.shadowsocks.App.Companion.app import com.github.shadowsocks.BuildConfig -import okhttp3.Dns as Okdns import org.xbill.DNS.* import java.net.Inet6Address import java.net.InetAddress import java.net.NetworkInterface import java.net.UnknownHostException -import java.util.* +import okhttp3.Dns as Okdns object Dns { private const val TAG = "Dns" - @Throws(Exception::class) - fun getDnsResolver(context: Context): String { - val dnsResolvers = getDnsResolvers(context) - if (dnsResolvers.isEmpty()) throw Exception("Couldn't find an active DNS resolver") - val dnsResolver = dnsResolvers.iterator().next().toString() - if (dnsResolver.startsWith("/")) return dnsResolver.substring(1) - return dnsResolver - } - - @Throws(Exception::class) - private fun getDnsResolvers(context: Context): Collection { - val addresses = ArrayList() - val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager - val classLinkProperties = Class.forName("android.net.LinkProperties") - val getActiveLinkPropertiesMethod = ConnectivityManager::class.java.getMethod("getActiveLinkProperties") - val linkProperties = getActiveLinkPropertiesMethod.invoke(connectivityManager) - if (linkProperties != null) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - val dnses = classLinkProperties.getMethod("getDnses").invoke(linkProperties) as Collection<*> - dnses.mapTo(addresses) { it as InetAddress } - } else addresses.addAll((linkProperties as LinkProperties).dnsServers) - } - return addresses - } - private val hasIPv6Support get() = try { val result = NetworkInterface.getNetworkInterfaces().asSequence().flatMap { it.inetAddresses.asSequence() } .count { it is Inet6Address && !it.isLoopbackAddress && !it.isLinkLocalAddress } > 0 diff --git a/mobile/src/main/java/com/github/shadowsocks/bg/LocalDnsService.kt b/mobile/src/main/java/com/github/shadowsocks/bg/LocalDnsService.kt index aae3a4834e..1d1d964435 100644 --- a/mobile/src/main/java/com/github/shadowsocks/bg/LocalDnsService.kt +++ b/mobile/src/main/java/com/github/shadowsocks/bg/LocalDnsService.kt @@ -47,14 +47,14 @@ object LocalDnsService { val data = data val profile = data.profile!! - fun makeDns(name: String, address: String, edns: Boolean = true): JSONObject { + fun makeDns(name: String, address: String, timeout: Int, edns: Boolean = true): JSONObject { val dns = JSONObject() .put("Name", name) .put("Address", (when (address.parseNumericAddress()) { is Inet6Address -> "[$address]" else -> address - }) + ":53") - .put("Timeout", 12) + })) + .put("Timeout", timeout) .put("EDNSClientSubnet", JSONObject().put("Policy", "disable")) if (edns) dns .put("Protocol", "tcp") @@ -73,18 +73,14 @@ object LocalDnsService { .put("MinimumTTL", 120) .put("CacheSize", 4096) val remoteDns = JSONArray(profile.remoteDns.split(",") - .mapIndexed { i, dns -> makeDns("UserDef-" + i, dns.trim()) }) + .mapIndexed { i, dns -> makeDns("UserDef-" + i, + dns.trim() + ":53", 9) }) val localDns = JSONArray(arrayOf( - makeDns("Primary-1", "119.29.29.29", false), - makeDns("Primary-2", "114.114.114.114", false) + makeDns("Primary-1", "119.29.29.29:53", 3, false), + makeDns("Primary-2", "114.114.114.114:53", 3, false), + makeDns("Primary-3", "208.67.222.222:443", 3, false) )) - this as Context - try { - val localLinkDns = Dns.getDnsResolver(this) - localDns.put(makeDns("Primary-3", localLinkDns, false)) - } catch (_: Exception) { } // ignore - when (profile.route) { Acl.BYPASS_CHN, Acl.BYPASS_LAN_CHN, Acl.GFWLIST, Acl.CUSTOM_RULES -> config .put("PrimaryDNS", localDns) diff --git a/mobile/src/main/java/com/github/shadowsocks/bg/ServiceNotification.kt b/mobile/src/main/java/com/github/shadowsocks/bg/ServiceNotification.kt index e4974a21a4..08d9f1a1e0 100644 --- a/mobile/src/main/java/com/github/shadowsocks/bg/ServiceNotification.kt +++ b/mobile/src/main/java/com/github/shadowsocks/bg/ServiceNotification.kt @@ -40,8 +40,7 @@ import java.util.* /** * Android < 8 VPN: always invisible because of VPN notification/icon - * Android 4.x other: always visible - * Android 5-7 other: only invisible in (possibly unsecure) lockscreen + * Android < 8 other: only invisible in (possibly unsecure) lockscreen * Android 8+: always visible due to system limitations * (user can choose to hide the notification in secure lockscreen or anywhere) */ @@ -74,8 +73,7 @@ class ServiceNotification(private val service: BaseService.Interface, profileNam .setColor(ContextCompat.getColor(service, R.color.material_primary_500)) .setTicker(service.getString(R.string.forward_success)) .setContentTitle(profileName) - .setContentIntent(PendingIntent.getActivity(service, 0, Intent(service, MainActivity::class.java) - .setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT), 0)) + .setContentIntent(MainActivity.pendingIntent(service)) .setSmallIcon(R.drawable.ic_service_active) private val style = NotificationCompat.BigTextStyle(builder) private var isVisible = true @@ -85,24 +83,22 @@ class ServiceNotification(private val service: BaseService.Interface, profileNam if (Build.VERSION.SDK_INT < 24) builder.addAction(R.drawable.ic_navigation_close, service.getString(R.string.stop), PendingIntent.getBroadcast(service, 0, Intent(Action.CLOSE), 0)) val power = service.getSystemService(Context.POWER_SERVICE) as PowerManager - update(if (if (Build.VERSION.SDK_INT >= 20) power.isInteractive else @Suppress("DEPRECATION") power.isScreenOn) - Intent.ACTION_SCREEN_ON else Intent.ACTION_SCREEN_OFF, true) + update(if (power.isInteractive) Intent.ACTION_SCREEN_ON else Intent.ACTION_SCREEN_OFF, true) val screenFilter = IntentFilter() screenFilter.addAction(Intent.ACTION_SCREEN_ON) screenFilter.addAction(Intent.ACTION_SCREEN_OFF) - if (visible && Build.VERSION.SDK_INT in 21 until 26) screenFilter.addAction(Intent.ACTION_USER_PRESENT) + if (visible && Build.VERSION.SDK_INT < 26) screenFilter.addAction(Intent.ACTION_USER_PRESENT) service.registerReceiver(lockReceiver, screenFilter) } private fun update(action: String, forceShow: Boolean = false) { if (forceShow || service.data.state == BaseService.CONNECTED) when (action) { Intent.ACTION_SCREEN_OFF -> { - setVisible(visible && Build.VERSION.SDK_INT < 21, forceShow) + setVisible(false, forceShow) unregisterCallback() // unregister callback to save battery } Intent.ACTION_SCREEN_ON -> { - setVisible(visible && (Build.VERSION.SDK_INT < 21 || !keyGuard.inKeyguardRestrictedInputMode()), - forceShow) + setVisible(visible && !keyGuard.inKeyguardRestrictedInputMode(), forceShow) service.data.binder.registerCallback(callback) service.data.binder.startListeningForBandwidth(callback) callbackRegistered = true diff --git a/mobile/src/main/java/com/github/shadowsocks/bg/VpnService.kt b/mobile/src/main/java/com/github/shadowsocks/bg/VpnService.kt index 4a456d5a65..c1f9470a89 100644 --- a/mobile/src/main/java/com/github/shadowsocks/bg/VpnService.kt +++ b/mobile/src/main/java/com/github/shadowsocks/bg/VpnService.kt @@ -24,12 +24,12 @@ import android.app.Service import android.content.Intent import android.content.pm.PackageManager import android.net.LocalSocket -import android.os.Build import android.os.IBinder import android.os.ParcelFileDescriptor import android.util.Log import com.github.shadowsocks.App.Companion.app import com.github.shadowsocks.JniHelper +import com.github.shadowsocks.MainActivity import com.github.shadowsocks.R import com.github.shadowsocks.VpnRequestActivity import com.github.shadowsocks.acl.Acl @@ -129,6 +129,7 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface { private fun startVpn(): Int { val profile = data.profile!! val builder = Builder() + .setConfigureIntent(MainActivity.pendingIntent(this)) .setSession(profile.formattedName) .setMtu(VPN_MTU) .addAddress(PRIVATE_VLAN.format(Locale.ENGLISH, "1"), 24) @@ -140,7 +141,7 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface { builder.addRoute("::", 0) } - if (Build.VERSION.SDK_INT >= 21 && profile.proxyApps) { + if (profile.proxyApps) { val me = packageName profile.individual.split('\n') .filter { it != me } diff --git a/mobile/src/main/java/com/github/shadowsocks/preference/BottomSheetPreferenceDialogFragment.kt b/mobile/src/main/java/com/github/shadowsocks/preference/BottomSheetPreferenceDialogFragment.kt index ec78a53317..ffd0d44697 100644 --- a/mobile/src/main/java/com/github/shadowsocks/preference/BottomSheetPreferenceDialogFragment.kt +++ b/mobile/src/main/java/com/github/shadowsocks/preference/BottomSheetPreferenceDialogFragment.kt @@ -29,6 +29,7 @@ import android.os.Bundle import android.provider.Settings import android.support.design.widget.BottomSheetDialog import android.support.v7.preference.PreferenceDialogFragmentCompat +import android.support.v7.widget.DefaultItemAnimator import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.RecyclerView import android.view.LayoutInflater @@ -107,10 +108,12 @@ class BottomSheetPreferenceDialogFragment : PreferenceDialogFragmentCompat() { recycler.setPadding(0, padding, 0, padding) recycler.setHasFixedSize(true) recycler.layoutManager = LinearLayoutManager(activity) + recycler.itemAnimator = DefaultItemAnimator() recycler.adapter = IconListAdapter(dialog) recycler.layoutParams = ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) dialog.setContentView(recycler) + dialog.findViewById(R.id.touch_outside)!!.isFocusable = false return dialog } diff --git a/mobile/src/main/java/com/github/shadowsocks/tasker/ConfigActivity.kt b/mobile/src/main/java/com/github/shadowsocks/tasker/ConfigActivity.kt index 68afd7c438..602f75d7bd 100644 --- a/mobile/src/main/java/com/github/shadowsocks/tasker/ConfigActivity.kt +++ b/mobile/src/main/java/com/github/shadowsocks/tasker/ConfigActivity.kt @@ -22,7 +22,6 @@ package com.github.shadowsocks.tasker import android.app.Activity import android.content.res.Resources -import android.os.Build import android.os.Bundle import android.support.v7.app.AppCompatActivity import android.support.v7.widget.DefaultItemAnimator @@ -71,13 +70,12 @@ class ConfigActivity : AppCompatActivity() { inner class ProfilesAdapter : RecyclerView.Adapter() { internal val profiles = ProfileManager.getAllProfiles()?.toMutableList() ?: mutableListOf() - private val name = "select_dialog_singlechoice_" + (if (Build.VERSION.SDK_INT >= 21) "material" else "holo") override fun onBindViewHolder(holder: ProfileViewHolder, position: Int) = if (position == 0) holder.bindDefault() else holder.bind(profiles[position - 1]) override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProfileViewHolder = ProfileViewHolder( LayoutInflater.from(parent.context).inflate(Resources.getSystem() - .getIdentifier(name, "layout", "android"), parent, false)) + .getIdentifier("select_dialog_singlechoice_material", "layout", "android"), parent, false)) override fun getItemCount(): Int = 1 + profiles.size } diff --git a/mobile/src/main/java/com/github/shadowsocks/utils/Utils.kt b/mobile/src/main/java/com/github/shadowsocks/utils/Utils.kt index 95b3b9b715..e328fcff35 100644 --- a/mobile/src/main/java/com/github/shadowsocks/utils/Utils.kt +++ b/mobile/src/main/java/com/github/shadowsocks/utils/Utils.kt @@ -9,7 +9,6 @@ import android.support.annotation.AttrRes import android.support.v4.app.Fragment import android.support.v4.app.FragmentManager import android.support.v7.util.SortedList -import android.view.accessibility.AccessibilityManager import android.util.TypedValue import com.github.shadowsocks.App.Companion.app import com.github.shadowsocks.JniHelper @@ -22,11 +21,6 @@ private val fieldChildFragmentManager by lazy { field } -fun isAccessibilityEnabled(context: Context): Boolean { - val am = context.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager - return am.isEnabled() -} - fun String.isNumericAddress() = JniHelper.parseNumericAddress(this) != null fun String.parseNumericAddress(): InetAddress? { val addr = JniHelper.parseNumericAddress(this) diff --git a/mobile/src/main/java/com/github/shadowsocks/widget/BottomMarginBehavior.kt b/mobile/src/main/java/com/github/shadowsocks/widget/BottomMarginBehavior.kt deleted file mode 100644 index e420f4dff5..0000000000 --- a/mobile/src/main/java/com/github/shadowsocks/widget/BottomMarginBehavior.kt +++ /dev/null @@ -1,40 +0,0 @@ -/******************************************************************************* - * * - * Copyright (C) 2017 by Max Lv * - * Copyright (C) 2017 by Mygod Studio * - * * - * 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 . * - * * - *******************************************************************************/ - -package com.github.shadowsocks.widget - -import android.content.Context -import android.support.design.widget.CoordinatorLayout -import android.util.AttributeSet -import android.view.View -import com.github.shadowsocks.R - -class BottomMarginBehavior : CoordinatorLayout.Behavior { - constructor() : super() - constructor(context: Context, attrs: AttributeSet) : super(context, attrs) - - override fun layoutDependsOn(parent: CoordinatorLayout, child: View, dependency: View): Boolean = - dependency.id == R.id.stat // sorry I'm too lazy to write an attribute - - override fun onDependentViewChanged(parent: CoordinatorLayout, child: View, dependency: View): Boolean { - (child.layoutParams as CoordinatorLayout.LayoutParams).bottomMargin = (parent.height - dependency.y).toInt() - return true - } -} diff --git a/mobile/src/main/java/com/github/shadowsocks/widget/MoveUpwardBehavior.kt b/mobile/src/main/java/com/github/shadowsocks/widget/ShrinkUpwardBehavior.kt similarity index 69% rename from mobile/src/main/java/com/github/shadowsocks/widget/MoveUpwardBehavior.kt rename to mobile/src/main/java/com/github/shadowsocks/widget/ShrinkUpwardBehavior.kt index 0bcc6f8bf4..9f550f949e 100644 --- a/mobile/src/main/java/com/github/shadowsocks/widget/MoveUpwardBehavior.kt +++ b/mobile/src/main/java/com/github/shadowsocks/widget/ShrinkUpwardBehavior.kt @@ -20,34 +20,27 @@ package com.github.shadowsocks.widget -import android.animation.Animator -import android.animation.AnimatorListenerAdapter import android.animation.ValueAnimator import android.content.Context -import android.os.Build import android.support.design.widget.CoordinatorLayout -import android.support.design.widget.SnackbarAnimation import android.support.design.widget.Snackbar -import android.support.v4.view.ViewCompat +import android.support.design.widget.SnackbarAnimation import android.util.AttributeSet import android.view.View - -import com.github.shadowsocks.utils.isAccessibilityEnabled +import android.view.accessibility.AccessibilityManager /** * Full credits go to: https://stackoverflow.com/a/35904421/2245107 */ -class MoveUpwardBehavior : CoordinatorLayout.Behavior { - constructor() : super() - constructor(context: Context, attrs: AttributeSet) : super(context, attrs) +class ShrinkUpwardBehavior(context: Context, attrs: AttributeSet) : CoordinatorLayout.Behavior(context, attrs) { + private val accessibility = context.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager override fun layoutDependsOn(parent: CoordinatorLayout, child: View, dependency: View): Boolean = - dependency is Snackbar.SnackbarLayout + dependency is Snackbar.SnackbarLayout override fun onDependentViewChanged(parent: CoordinatorLayout, child: View, dependency: View): Boolean { - val params = child.layoutParams - params.height = parent.height - dependency.height - child.layoutParams = params + child.layoutParams.height = dependency.y.toInt() + child.requestLayout() return true } @@ -55,25 +48,18 @@ class MoveUpwardBehavior : CoordinatorLayout.Behavior { * Based on BaseTransientBottomBar.animateViewOut (support lib 27.0.2). */ override fun onDependentViewRemoved(parent: CoordinatorLayout, child: View, dependency: View) { - if (!isAccessibilityEnabled(parent.getContext())) { + if (accessibility.isEnabled) child.layoutParams.height = parent.height else { val animator = ValueAnimator() val start = child.height animator.setIntValues(start, parent.height) animator.interpolator = SnackbarAnimation.FAST_OUT_SLOW_IN_INTERPOLATOR animator.duration = SnackbarAnimation.ANIMATION_DURATION - animator.addUpdateListener(object : ValueAnimator.AnimatorUpdateListener { - override fun onAnimationUpdate(animator: ValueAnimator) { - val currentValue = animator.animatedValue as Int - val params = child.layoutParams - params.height = currentValue - child.layoutParams = params - } - }) + @Suppress("NAME_SHADOWING") + animator.addUpdateListener { animator -> + child.layoutParams.height = animator.animatedValue as Int + child.requestLayout() + } animator.start() - } else { - val params = child.layoutParams - params.height = parent.height - child.layoutParams = params } } } diff --git a/mobile/src/main/res/drawable-hdpi/ic_navigation_close.webp b/mobile/src/main/res/drawable-hdpi/ic_navigation_close.webp deleted file mode 100644 index 541301f06d..0000000000 Binary files a/mobile/src/main/res/drawable-hdpi/ic_navigation_close.webp and /dev/null differ diff --git a/mobile/src/main/res/drawable-hdpi/ic_service_active.webp b/mobile/src/main/res/drawable-hdpi/ic_service_active.webp deleted file mode 100644 index 65182a59d8..0000000000 Binary files a/mobile/src/main/res/drawable-hdpi/ic_service_active.webp and /dev/null differ diff --git a/mobile/src/main/res/drawable-mdpi/ic_navigation_close.webp b/mobile/src/main/res/drawable-mdpi/ic_navigation_close.webp deleted file mode 100644 index 2c66e48688..0000000000 Binary files a/mobile/src/main/res/drawable-mdpi/ic_navigation_close.webp and /dev/null differ diff --git a/mobile/src/main/res/drawable-mdpi/ic_service_active.webp b/mobile/src/main/res/drawable-mdpi/ic_service_active.webp deleted file mode 100644 index 0182a994bf..0000000000 Binary files a/mobile/src/main/res/drawable-mdpi/ic_service_active.webp and /dev/null differ diff --git a/mobile/src/main/res/drawable-v21/background_profile.xml b/mobile/src/main/res/drawable-v21/background_profile.xml deleted file mode 100644 index 3509fa3228..0000000000 --- a/mobile/src/main/res/drawable-v21/background_profile.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/mobile/src/main/res/drawable-v21/background_selectable.xml b/mobile/src/main/res/drawable-v21/background_selectable.xml deleted file mode 100644 index caca653d45..0000000000 --- a/mobile/src/main/res/drawable-v21/background_selectable.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - diff --git a/mobile/src/main/res/drawable-v21/background_stat.xml b/mobile/src/main/res/drawable-v21/background_stat.xml deleted file mode 100644 index 48bdd29f9c..0000000000 --- a/mobile/src/main/res/drawable-v21/background_stat.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/mobile/src/main/res/drawable-xhdpi/ic_navigation_close.webp b/mobile/src/main/res/drawable-xhdpi/ic_navigation_close.webp deleted file mode 100644 index c1d0fd16e9..0000000000 Binary files a/mobile/src/main/res/drawable-xhdpi/ic_navigation_close.webp and /dev/null differ diff --git a/mobile/src/main/res/drawable-xhdpi/ic_service_active.webp b/mobile/src/main/res/drawable-xhdpi/ic_service_active.webp deleted file mode 100644 index 0e0bbf50cc..0000000000 Binary files a/mobile/src/main/res/drawable-xhdpi/ic_service_active.webp and /dev/null differ diff --git a/mobile/src/main/res/drawable-xxhdpi/ic_navigation_close.webp b/mobile/src/main/res/drawable-xxhdpi/ic_navigation_close.webp deleted file mode 100644 index 1ae09899ee..0000000000 Binary files a/mobile/src/main/res/drawable-xxhdpi/ic_navigation_close.webp and /dev/null differ diff --git a/mobile/src/main/res/drawable-xxhdpi/ic_service_active.webp b/mobile/src/main/res/drawable-xxhdpi/ic_service_active.webp deleted file mode 100644 index bb2c5bc2ea..0000000000 Binary files a/mobile/src/main/res/drawable-xxhdpi/ic_service_active.webp and /dev/null differ diff --git a/mobile/src/main/res/drawable-xxxhdpi/ic_navigation_close.webp b/mobile/src/main/res/drawable-xxxhdpi/ic_navigation_close.webp deleted file mode 100644 index 439b8fe896..0000000000 Binary files a/mobile/src/main/res/drawable-xxxhdpi/ic_navigation_close.webp and /dev/null differ diff --git a/mobile/src/main/res/drawable/background_profile.xml b/mobile/src/main/res/drawable/background_profile.xml index 2859251427..3509fa3228 100644 --- a/mobile/src/main/res/drawable/background_profile.xml +++ b/mobile/src/main/res/drawable/background_profile.xml @@ -15,18 +15,11 @@ - + + - - - - - - - - - + diff --git a/mobile/src/main/res/drawable/background_selectable.xml b/mobile/src/main/res/drawable/background_selectable.xml index e4b7a42874..b88100341a 100644 --- a/mobile/src/main/res/drawable/background_selectable.xml +++ b/mobile/src/main/res/drawable/background_selectable.xml @@ -1,8 +1,12 @@ - - - + + + + + + + - + + diff --git a/mobile/src/main/res/drawable/background_stat.xml b/mobile/src/main/res/drawable/background_stat.xml index fe7ba6d445..7c56f04cfc 100644 --- a/mobile/src/main/res/drawable/background_stat.xml +++ b/mobile/src/main/res/drawable/background_stat.xml @@ -1,17 +1,7 @@ - - - - - - - - - - - - + + diff --git a/mobile/src/main/res/drawable/ic_action_delete.xml b/mobile/src/main/res/drawable/ic_action_delete.xml index 89c372c3ed..0e9d1eb3de 100644 --- a/mobile/src/main/res/drawable/ic_action_delete.xml +++ b/mobile/src/main/res/drawable/ic_action_delete.xml @@ -2,7 +2,8 @@ android:width="24dp" android:height="24dp" android:viewportWidth="24.0" - android:viewportHeight="24.0"> + android:viewportHeight="24.0" + android:tint="?attr/colorControlNormal"> diff --git a/mobile/src/main/res/drawable-anydpi-v21/ic_service_active.xml b/mobile/src/main/res/drawable/ic_service_active.xml similarity index 100% rename from mobile/src/main/res/drawable-anydpi-v21/ic_service_active.xml rename to mobile/src/main/res/drawable/ic_service_active.xml diff --git a/mobile/src/main/res/drawable/ic_service_connected.xml b/mobile/src/main/res/drawable/ic_service_connected.xml index f5ce5d49e4..18c8960701 100644 --- a/mobile/src/main/res/drawable/ic_service_connected.xml +++ b/mobile/src/main/res/drawable/ic_service_connected.xml @@ -2,9 +2,7 @@ + android:drawable="@drawable/ic_service_busy"> + android:drawable="@drawable/ic_service_idle"> + android:drawable="@drawable/ic_service_idle"> + android:drawable="@drawable/ic_service_busy"> + app:layout_behavior="com.github.shadowsocks.widget.ShrinkUpwardBehavior"> diff --git a/mobile/src/main/res/menu/custom_rules_menu.xml b/mobile/src/main/res/menu/custom_rules_menu.xml index 4fe4f60234..db17f76ffe 100644 --- a/mobile/src/main/res/menu/custom_rules_menu.xml +++ b/mobile/src/main/res/menu/custom_rules_menu.xml @@ -1,41 +1,6 @@ - - - - - - - - + + + + + + diff --git a/mobile/src/main/res/values-fa/strings.xml b/mobile/src/main/res/values-fa/strings.xml index 3f8619fde2..ff3bad5c5c 100644 --- a/mobile/src/main/res/values-fa/strings.xml +++ b/mobile/src/main/res/values-fa/strings.xml @@ -4,7 +4,7 @@ "پروفایل" -"در حال آزمایش..." +"در حال آزمایش…" "نام پروفایل" @@ -20,7 +20,7 @@ "آدرس سرور نامعتبر است" "اتصال به سرور ناموفق بود!" "غیرفعال‌کردن" -"در حال قطع‌کردن اتصال..." +"در حال قطع‌کردن اتصال…" "هیچ پروفایل معتبری یافت نشد!" @@ -33,7 +33,6 @@ "پروفایل‌ها" "تنظیمات" "سوالات مداول" -"ریست" "درباره ما" "ویرایش" "به اشتراک گذاری" @@ -52,9 +51,8 @@ "فرستاده‌شده:" "دریافت‌شده:" -"در حال اتصال..." +"در حال اتصال…" "وصل شد. برای بررسی اتصال ضربه (Tap) بزنید." -"اتصال برقرار است." "اتصال برقرار نیست!" "همه" \ No newline at end of file diff --git a/mobile/src/main/res/values-fr/strings.xml b/mobile/src/main/res/values-fr/strings.xml index 55344e5192..565e57036a 100644 --- a/mobile/src/main/res/values-fr/strings.xml +++ b/mobile/src/main/res/values-fr/strings.xml @@ -1,3 +1,35 @@ + + +"Profil" +"Impossible de détecter la connexion Internet :%s" +"Internet Indisponible" +"Code de l'Erreur : #%d" +"Serveur" +"Mot de passe" +"Échec de la connexion au serveur distant" + + +"Fermer" +"Veuillez sélectionner un profil" + + +"Profils" +"Paramètres" +"À propos" +"Partager" +"Ajouter un Profil" +"Appliquer les Paramètres à tous les Profils" +"Exporter vers le presse-papiers" +"Importer depuis le presse-papiers" +"Supprimer" +"Êtes-vous sûr de vouloir supprimer ce profil?" +"QR Code/NFC" +"Scanner le QR Code" + +"Supprimé" +"%d éléments supprimés" + +"Annuler" \ No newline at end of file diff --git a/mobile/src/main/res/values-ja/strings.xml b/mobile/src/main/res/values-ja/strings.xml index 02632ef1fb..9a9383e8f9 100644 --- a/mobile/src/main/res/values-ja/strings.xml +++ b/mobile/src/main/res/values-ja/strings.xml @@ -1,6 +1,5 @@ -"Shadowsocks" "ON/OFF" @@ -9,7 +8,6 @@ "リモートDNS" "送信済み: \t%3$s\t↑\t%1$s/s 受信済み: \t%4$s\t↓\t%2$s/s" - "接続状況確認" "テスト中…" "成功: %dmsの遅延" @@ -35,10 +33,8 @@ "リモートサーバーに IPv6 パケットを転送" "プロキシ方式" "中国本土からアクセス不可なアドレス以外を迂回する" - -"アプリ別のプロキシを使用" - -"アプリ別でプロキシを指定、Android 4.x では必ずNATモードを使用して下さい" +"アプリ別のVPNモードを使用" +"アプリ別のVPNモードを設定" "ON" "バイパスモード(迂回モード)" "このモードを選択した場合、指定したアプリのパケットはプロキシを経由しない" @@ -74,9 +70,7 @@ "プロファイル" "オプション設定" "よくある質問" -"https://github.com/shadowsocks/shadowsocks-android/blob/master/.github/faq.md" "本アプリについて" -"Shadowsocks %s" "編集" "共有" "プロファイルを追加" @@ -127,10 +121,7 @@ "中国本土のアドレスを迂回する" "LAN 及び中国本土のアドレスを迂回する" "中国本土のアドレス以外を迂回する" - -"選択したアプリにプロキシを設定する" - -"URL/サブネット/ホスト名 PCRE パターン" +"サブネットまたはホスト名のPCREパターン" "ドメイン及び全てのサブドメイン" @@ -148,7 +139,6 @@ "アドバンス" "サービスモード" "プロキシのみ" -"VPN" "トランスプロキシ" "SOCKS5プロキシポート" "ローカルDNSポート" @@ -157,4 +147,7 @@ "トランスプロキシサービス" "VPNサービス作成のアクセス許可が拒否されました" "起動時にShadowsockを有効。 VPN常時接続の使用をお勧めします" - +"ダイレクトブート" +"デバイスがロック解除される前にShadowsocksの自動起動を許可(選択されたプロファイル情報はより少ない保護を受けることになります)" +"オンライン設定のURL" + \ No newline at end of file diff --git a/mobile/src/main/res/values-ko/strings.xml b/mobile/src/main/res/values-ko/strings.xml index ea1c0ffbcf..4732e95735 100644 --- a/mobile/src/main/res/values-ko/strings.xml +++ b/mobile/src/main/res/values-ko/strings.xml @@ -1,6 +1,5 @@ -"Shadowsocks" "켜기/끄기" @@ -9,7 +8,9 @@ "원격 DNS" "송신: \t%3$s\t↑\t%1$s/s 수신: \t%4$s\t↓\t%2$s/s" - + +"바이트" + "인터넷 연결 상태 검사하기" "검사 중…" "성공: 지연시간 %dms" @@ -34,11 +35,10 @@ "IPv6 라우팅" "IPv6 트래픽도 원격으로 리다이렉트 합니다" "라우팅 대상" -"GFW List" + "원하는 앱만 프락시 적용하기" - -"선택한 앱에만 프락시를 적용합니다. Android 4.x 이하에서는 NAT 모드를 써야 합니다." +"선택한 앱에만 프락시를 적용합니다" "활성화" "선택된 앱들만 프록시 적용 제외하기" "이 옵션을 활성화하면 선택된 앱들은 프락시를 사용하지 않습니다" @@ -55,7 +55,6 @@ "원격 서버에 접속하는 데 실패했습니다" "중지" "종료 중…" -"%s" "VPN 서비스를 시작하는 데 실패했습니다. 장치를 재시작해 보세요." "올바른 프로필 데이터를 찾을 수 없습니다" @@ -74,13 +73,10 @@ "프로필" "설정" "자주 묻는 질문" -"https://github.com/shadowsocks/shadowsocks-android/blob/master/.github/faq.md" "이 앱에 대하여" -"Shadowsocks %s" "수정" "공유" "프로필 추가" -"Apply Settings to All Profiles" "클립보드로 내보내기" "클립보드에서 불러오기" "성공적으로 내보냈습니다" @@ -102,34 +98,15 @@ "실행 취소" - -"Start the service" -"Connect to the current server" -"Connect to %s" -"Switch to %s" -"Use the current profile" - "송신:" "수신:" "연결 중…" "연결 완료. 탭 하면 연결 상태를 검사합니다." -"Not connected" "사용자 정의 규칙" -"Selection…" -"Add rule(s)…" -"Edit rule" -"All" -"Bypass LAN" -"Bypass mainland China" -"Bypass LAN & mainland China" -"China List" -"Configure VPN mode for selected apps" - -"URL, 서브넷 혹은 호스트 이름 PCRE 패턴" -"Domain name and all its subdomain names" +"서브넷 혹은 호스트 이름 PCRE 패턴" "플러그인" @@ -139,21 +116,4 @@ "경고: 이 플러그인은 신뢰할 수 있는 출처에서 온 것이 아닌 것 같습니다" "플러그인: %s" "QR 코드를 읽어 들이려면 카메라 권한이 필요합니다" - - -"VPN Service" -"Manual Settings" -"Advanced" -"Service mode" -"Proxy only" -"VPN" -"Transproxy" -"SOCKS5 proxy port" -"Local DNS port" -"Transproxy port" -"Proxy Service" -"Transproxy Service" -"Permission denied to create a VPN service" -"Enable Shadowsocks on startup. Recommended to use always-on VPN - instead" - + \ No newline at end of file diff --git a/mobile/src/main/res/values-ru/strings.xml b/mobile/src/main/res/values-ru/strings.xml index 20e46227e7..9513e618a6 100644 --- a/mobile/src/main/res/values-ru/strings.xml +++ b/mobile/src/main/res/values-ru/strings.xml @@ -1,15 +1,13 @@ -"Shadowsocks" "Подключение" "Профиль" "Переключить на другой профиль или добавить новые" "Удалённый DNS" -"Отправлено: \t%3$s\t↑\t%1$s/s -Получено: \t%4$s\t↓\t%2$s/s" - +"Отправлено: \t%3$s\t↑\t%1$s +Получено: \t\t\t%4$s\t↓\t%2$s" "байт" "байта" @@ -27,11 +25,11 @@ "Настройки Сервера" -"Имя Профиля" +"Имя профиля" "Сервер" "Удалённый порт" "Пароль" -"Метод Шифрования" +"Метод шифрования" "Дополнительные Настройки" @@ -41,14 +39,12 @@ "Перенаправлять трафик IPv6 на удалённый сервер" "Маршрут" "Список GFW" - -"Прокси для выбранных приложений" - -"Установить прокси для выбранных приложений, требует режим NAT под Android 4.x" +"Режим VPN для выбранных приложений" +"Выбрать приложения, для которых нужно использовать режим VPN" "Вкл" "В обход прокси" "Включите эту опцию для работы выбранных приложений в обход прокси" -"Авто Подключение" +"Авто-подключение" "Запускать Shadowsocks при включении" "Переключение требует наличия ROOT прав" "Неподдерживаемая версия ядра: %s < 3.7.1" @@ -61,9 +57,8 @@ "Ошибка при подключении к удалённому серверу" "Остановить" "Останавливается…" -"%s" "Не удалось запустить службу VPN. Возможно, требуется перезагрузить ваше устройство." -"No valid profile data found." +"Не найдено действительных данных профиля." "Да" @@ -79,13 +74,13 @@ "Профили" "Настройки" -"FAQ" -"https://github.com/shadowsocks/shadowsocks-android/blob/master/.github/faq.md" +"ЧаВо" +"https://github.com/shadowsocks/shadowsocks-android/blob/master/.github/faq.ru.md" "О приложении" "Shadowsocks %s" "Изменить" "Поделиться" -"Добавить Профиль" +"Добавить профиль" "Применить настройки для всех профилей" "Экспортировать в буфер обмена" "Импортировать из буфера обмена" @@ -101,7 +96,7 @@ "Удалить" "Вы уверены, что хотите удалить этот профиль?" "QR-код/NFC" -"Добавить этот Профиль Shadowsocks?" +"Добавить этот профиль Shadowsocks?" "Сканировать QR-код" "Удалено %d элементов" @@ -135,10 +130,7 @@ "Все, кроме Китая" "Все, кроме LAN и Китая" "Список Китай" - -"Установить прокси для выбранных приложений" - -"URL/Подсеть/Регулярное выражение (PCRE) имени хоста" +"Подсеть или имя хоста, записанное регулярным выражением (PCRE)" "Доменное имя и все его поддомены" @@ -151,19 +143,21 @@ "Разрешение камеры требуется для сканирования QR код." -"VPN Service" -"Manual Settings" -"Advanced" -"Service mode" -"Proxy only" -"VPN" -"Transproxy" -"SOCKS5 proxy port" -"Local DNS port" -"Transproxy port" -"Proxy Service" -"Transproxy Service" -"Permission denied to create a VPN service" -"Enable Shadowsocks on startup. Recommended to use always-on VPN - instead" - +"Служба VPN" +"Ручные настройки" +"Дополнительно" +"Режим" +"Только прокси" +"Прозрачный прокси" +"Порт SOCKS5 прокси" +"Локальный порт DNS" +"Порт прозрачного прокси" +"Служба прокси" +"Служба прозрачный прокси" +"Нет разрешения на создание VPN соединения" +"Включить Shadowsocks во время запуска. Лучше использовать режим постоянной VPN." +"%s/с" +"Непосредственная загрузка" +"Позволить Shadowsocks запускаться до того, как устройство будет разблокировано (данные выбранного профиля будут хуже защищены)" +"URL конфигурации" + \ No newline at end of file diff --git a/mobile/src/main/res/values-v21/dimen.xml b/mobile/src/main/res/values-v21/dimen.xml deleted file mode 100644 index 605379c325..0000000000 --- a/mobile/src/main/res/values-v21/dimen.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - -28dp - diff --git a/mobile/src/main/res/values-zh-rCN/strings.xml b/mobile/src/main/res/values-zh-rCN/strings.xml index 70bc1efb8d..a8e505e352 100644 --- a/mobile/src/main/res/values-zh-rCN/strings.xml +++ b/mobile/src/main/res/values-zh-rCN/strings.xml @@ -1,27 +1,19 @@ - "影梭" - "开关" "配置文件" - "新建或切换到其他配置文件" - "远程 DNS" - -"上传: \t%3$s\t↑\t%1$s/s -下载: \t%4$s\t↓\t%2$s/s" - +"上传: \t%3$s\t↑\t%1$s +下载: \t%4$s\t↓\t%2$s" "检查网络连接" "测试中…" "连接成功:延时 %d 毫秒" - "失败:%s" "无互联网连接" - "状态码无效(#%d)" @@ -29,7 +21,6 @@ "配置名称" - "服务器" "远程端口" "密码" @@ -44,7 +35,6 @@ "路由" "GFW 列表" "分应用 VPN" - "允许部分应用绕过 VPN" "启用" "绕行模式" @@ -81,7 +71,6 @@ "配置文件" "设置选项" "常见问题" -"https://github.com/shadowsocks/shadowsocks-android/blob/master/.github/faq.md" "关于" "影梭 (Shadowsocks) %s" "编辑" @@ -133,12 +122,8 @@ "绕过中国大陆地址" "绕过局域网及中国大陆地址" "仅代理中国大陆地址" - -"为应用程序分别设置代理" - -"URL/子网/域名 PCRE 正则表达式" +"子网/域名 PCRE 正则表达式" "域名及其子域名" -配置文件地址 "插件" @@ -155,7 +140,6 @@ "高级选项" "服务模式" "仅代理" -"VPN" "透明代理" "SOCKS5 代理端口" "本地 DNS 端口" @@ -164,4 +148,7 @@ "透明代理模式" "创建 VPN 服务权限不足" "允许 Shadowsocks 随系统启动,建议使用始终开启的 VPN" - +"直接启动" +"允许在设备解锁前启动服务(选中的配置信息会不那么安全)" +"在线规则文件 URL" + \ No newline at end of file diff --git a/mobile/src/main/res/values-zh-rTW/strings.xml b/mobile/src/main/res/values-zh-rTW/strings.xml index c382d25dd9..c5d54a33d5 100644 --- a/mobile/src/main/res/values-zh-rTW/strings.xml +++ b/mobile/src/main/res/values-zh-rTW/strings.xml @@ -37,10 +37,8 @@ "向遠端重新導向 IPv6 流量" "路由" "GFW List" - -"個別應用程式的 Proxy" - -"為已選擇的應用程式設定 Proxy,在 Android 4.X 以下需要開啟 NAT 模式" +"個別應用程式的 VPN" +"為已選擇的應用程式設定 VPN" "開" "略過模式" "啟用此選項,會略過已選擇的應用程式" @@ -57,7 +55,6 @@ "連線至遠端伺服器失敗" "停止" "關閉中…" -"%s" "VPN 服務啟動失敗。您或許需要重新啟動您的裝置。" "未找到有效的設定檔資料。" @@ -76,7 +73,6 @@ "設定檔" "設定" "常見問題" -"https://github.com/shadowsocks/shadowsocks-android/blob/master/.github/faq.md" "關於" "Shadowsocks %s" "編輯" @@ -128,12 +124,8 @@ "略過中國大陸" "略過區域網路及中國大陸" "China List" - -"為已選擇的應用程式設定 Proxy" - -"URL/子網路/主機名稱 PCRE 模式" +"子網路/主機名稱 PCRE 模式" "網域及其所有子網域" -配置文件地址 "外掛程式" @@ -150,7 +142,6 @@ "高級" "服務模式" "仅代理" -"VPN" "透明代理" "SOCKS5 代理連接埠" "本地 DNS 連接埠" @@ -159,4 +150,4 @@ "透明代理服務" "没有權限創建 VPN 服務" "允許 Shadowsocks 隨系統啟動,建議使用始終開啟的 VPN" - + \ No newline at end of file diff --git a/mobile/src/main/res/values/dimen.xml b/mobile/src/main/res/values/dimen.xml index ce2156f8c2..e081d68327 100644 --- a/mobile/src/main/res/values/dimen.xml +++ b/mobile/src/main/res/values/dimen.xml @@ -1,8 +1,6 @@ - 480dp 250dp 8dp 8dp - -44dp diff --git a/mobile/src/main/res/xml/pref_profile.xml b/mobile/src/main/res/xml/pref_profile.xml index 8c8bbff908..3a0b4b47ba 100644 --- a/mobile/src/main/res/xml/pref_profile.xml +++ b/mobile/src/main/res/xml/pref_profile.xml @@ -13,6 +13,7 @@ + android:viewportHeight="24.0" + android:tint="?attr/colorControlNormal"> diff --git a/plugin/src/main/res/layout/toolbar_light_dark.xml b/plugin/src/main/res/layout/toolbar_light_dark.xml index ae61afe459..d4dad21c07 100644 --- a/plugin/src/main/res/layout/toolbar_light_dark.xml +++ b/plugin/src/main/res/layout/toolbar_light_dark.xml @@ -6,6 +6,7 @@ android:layout_width="match_parent" android:background="?attr/colorPrimary" android:elevation="4dp" + android:touchscreenBlocksFocus="false" android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" app:popupTheme="@style/ThemeOverlay.AppCompat.Light" android:id="@+id/toolbar" /> diff --git a/plugin/src/main/res/values-ja/strings.xml b/plugin/src/main/res/values-ja/strings.xml index 0051b9c435..72199d0236 100644 --- a/plugin/src/main/res/values-ja/strings.xml +++ b/plugin/src/main/res/values-ja/strings.xml @@ -1,15 +1,9 @@ - - "サーバー設定" - - -"ファンクション設定" - - +"機能設定" +"変更は保存されておりません、保存しますか?" "はい" "いいえ" -"変更は適応されておりません、保存しますか?" -"適用" +"適応" \ No newline at end of file diff --git a/plugin/src/main/res/values-ru/strings.xml b/plugin/src/main/res/values-ru/strings.xml index 0e708a6e80..ea980633f9 100644 --- a/plugin/src/main/res/values-ru/strings.xml +++ b/plugin/src/main/res/values-ru/strings.xml @@ -1,15 +1,9 @@ - - -"Настройки Сервера" - - -"Дополнительные Настройки" - - +"Настройки сервера" +"Настройки опций" +"Сохранить изменения?" "Да" "Нет" -"Изменения не сохранены. Сохранить?" "Применить" \ No newline at end of file diff --git a/plugin/src/main/res/values-zh-rCN/strings.xml b/plugin/src/main/res/values-zh-rCN/strings.xml index 8e2ef95450..13d9d233c0 100644 --- a/plugin/src/main/res/values-zh-rCN/strings.xml +++ b/plugin/src/main/res/values-zh-rCN/strings.xml @@ -1,15 +1,9 @@ - - "服务器设置" - - "功能设置" - - +"是否要保存修改?" "是" "否" -"保存修改吗?" "应用" \ No newline at end of file diff --git a/plugin/src/main/res/values/styles.xml b/plugin/src/main/res/values/styles.xml index 30a2ae4714..e540a020e8 100644 --- a/plugin/src/main/res/values/styles.xml +++ b/plugin/src/main/res/values/styles.xml @@ -1,13 +1,14 @@ diff --git a/release.sh b/release.sh new file mode 100755 index 0000000000..bc7727defd --- /dev/null +++ b/release.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +release=$1 +cp mobile/build/outputs/apk/release/mobile-armeabi-v7a-release.apk shadowsocks-armeabi-v7a-${release}.apk +cp mobile/build/outputs/apk/release/mobile-arm64-v8a-release.apk shadowsocks-arm64-v8a-${release}.apk +cp mobile/build/outputs/apk/release/mobile-x86-release.apk shadowsocks-x86-${release}.apk +cp mobile/build/outputs/apk/release/mobile-universal-release.apk shadowsocks-universal-${release}.apk