Unverified Commit 98388759 authored by initrider's avatar initrider Committed by GitHub

Merge pull request #2 from shadowsocks/master

Sync
parents 25aacd20 1e5aabf8
......@@ -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)
......
......@@ -24,3 +24,6 @@ core/src/overture/src/gopkg.in
# work in progress
tv/
things/
# release apks
*.apk
## 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.
<a href="https://play.google.com/store/apps/details?id=com.github.shadowsocks"><img src="https://play.google.com/intl/en_us/badges/images/generic/en-play-badge.png" height="48"></a>
......
// 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"
}
}
......
......@@ -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)
}
}
......
......@@ -43,8 +43,6 @@ import android.support.annotation.NonNull;
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
......
APP_ABI := armeabi-v7a arm64-v8a x86
APP_PLATFORM := android-19
APP_PLATFORM := android-21
APP_STL := c++_static
NDK_TOOLCHAIN_VERSION := clang
......@@ -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))
......
Subproject commit afce1b3f426b2c8a746fd8e7b19973e8c15ec327
Subproject commit 6040be9d0af812382934defe317c2e28434494ad
@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"
......@@ -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
......
@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
Subproject commit fae912215d944d9536fb8445803c430219ee8218
Subproject commit 466f9902c9779b8801f521f88db1d1522c880c26
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
}
}
......@@ -171,9 +171,11 @@
<service android:name="com.evernote.android.job.v14.PlatformAlarmService"
android:process=":bg"
android:directBootAware="true"/>
<service android:name="com.evernote.android.job.gcm.PlatformGcmService"
<service android:name="com.evernote.android.job.v14.PlatformAlarmServiceExact"
android:process=":bg"
android:directBootAware="true"/>
<service android:name="com.evernote.android.job.gcm.PlatformGcmService"
tools:node="remove"/>
<service android:name="com.evernote.android.job.JobRescheduleService"
android:process=":bg"
android:directBootAware="true"/>
......
......@@ -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<Locale>(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)
......
......@@ -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<CrossFadeSlidingPaneLayout>? = 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<CrossFadeSlidingPaneLayout>()
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() {
......
......@@ -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
......
......@@ -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) { }
......
......@@ -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<Any>()
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<Any>
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<Spinner, EditText, AlertDialog.Builder> {
......@@ -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
}
}
......@@ -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<InetAddress> {
val addresses = ArrayList<InetAddress>()
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
......
......@@ -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)
......
......@@ -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
......
......@@ -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 }
......
......@@ -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<View>(R.id.touch_outside)!!.isFocusable = false
return dialog
}
......
......@@ -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<ProfileViewHolder>() {
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
}
......
......@@ -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)
......
/*******************************************************************************
* *
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
package com.github.shadowsocks.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<View> {
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
}
}
......@@ -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<View> {
constructor() : super()
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
class ShrinkUpwardBehavior(context: Context, attrs: AttributeSet) : CoordinatorLayout.Behavior<View>(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<View> {
* 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
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="@color/material_primary_300" />
</shape>
</item>
<item>
<selector android:enterFadeDuration="@android:integer/config_mediumAnimTime"
android:exitFadeDuration="@android:integer/config_mediumAnimTime">
<item android:state_selected="true">
<shape android:shape="rectangle">
<solid android:color="@color/material_green_700" />
</shape>
</item>
</selector>
</item>
<!-- android:start not available until API 23 -->
<item android:left="8dp">
<shape android:shape="rectangle">
<solid android:color="@android:color/white" />
</shape>
</item>
<item android:drawable="?android:attr/selectableItemBackground"/>
</layer-list>
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="?android:colorControlHighlight">
<item>
<selector android:enterFadeDuration="@android:integer/config_mediumAnimTime"
android:exitFadeDuration="@android:integer/config_mediumAnimTime">
<item android:state_selected="true">
<color android:color="@color/material_primary_100"/>
</item>
</selector>
</item>
</ripple>
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="?android:colorControlHighlight">
<item>
<color android:color="@color/material_primary_300"/>
</item>
</ripple>
......@@ -15,18 +15,11 @@
</item>
</selector>
</item>
<item android:left="@dimen/profile_padding">
<!-- android:start not available until API 23 -->
<item android:left="8dp">
<shape android:shape="rectangle">
<solid android:color="@android:color/white" />
</shape>
</item>
<item>
<selector>
<item android:state_pressed="true">
<shape android:shape="rectangle">
<solid android:color="#12000000" />
</shape>
</item>
</selector>
</item>
<item android:drawable="?android:attr/selectableItemBackground"/>
</layer-list>
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"
android:enterFadeDuration="@android:integer/config_mediumAnimTime"
android:exitFadeDuration="@android:integer/config_mediumAnimTime">
<item android:state_selected="true">
<color android:color="@color/material_primary_100"/>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<selector android:enterFadeDuration="@android:integer/config_mediumAnimTime"
android:exitFadeDuration="@android:integer/config_mediumAnimTime">
<item android:state_selected="true">
<color android:color="@color/material_primary_100"/>
</item>
</selector>
</item>
</selector>
<item android:drawable="?android:attr/selectableItemBackground"/>
</layer-list>
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="@color/material_primary_300" />
</shape>
</item>
<item>
<selector>
<item android:state_pressed="true">
<shape android:shape="rectangle">
<solid android:color="#12000000" />
</shape>
</item>
</selector>
<color android:color="@color/material_primary_300"/>
</item>
<item android:drawable="?android:attr/selectableItemBackground"/>
</layer-list>
......@@ -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">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"/>
......
......@@ -2,9 +2,7 @@
<animated-vector
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
xmlns:tools="http://schemas.android.com/tools"
android:drawable="@drawable/ic_service_busy"
tools:ignore="NewApi">
android:drawable="@drawable/ic_service_busy">
<target android:name="path">
<aapt:attr name="android:animation">
<objectAnimator
......
......@@ -2,9 +2,7 @@
<animated-vector
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
xmlns:tools="http://schemas.android.com/tools"
android:drawable="@drawable/ic_service_idle"
tools:ignore="NewApi">
android:drawable="@drawable/ic_service_idle">
<target android:name="strike_thru_path">
<aapt:attr name="android:animation">
<objectAnimator
......
......@@ -2,9 +2,7 @@
<animated-vector
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
xmlns:tools="http://schemas.android.com/tools"
android:drawable="@drawable/ic_service_idle"
tools:ignore="NewApi">
android:drawable="@drawable/ic_service_idle">
<target android:name="strike_thru_path">
<aapt:attr name="android:animation">
<objectAnimator
......
......@@ -2,9 +2,7 @@
<animated-vector
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
xmlns:tools="http://schemas.android.com/tools"
android:drawable="@drawable/ic_service_busy"
tools:ignore="NewApi">
android:drawable="@drawable/ic_service_busy">
<target android:name="path">
<aapt:attr name="android:animation">
<objectAnimator
......
......@@ -12,7 +12,7 @@
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="com.github.shadowsocks.widget.MoveUpwardBehavior">
app:layout_behavior="com.github.shadowsocks.widget.ShrinkUpwardBehavior">
<FrameLayout
android:id="@+id/fragment_holder"
......@@ -148,7 +148,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginTop="@dimen/fab_margin_top"
android:layout_marginTop="-28dp"
android:elevation="6dp"
app:layout_anchor="@id/stat"
app:layout_anchorGravity="top|end">
......
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/selection"
android:alphabeticShortcut="s"
android:icon="?attr/actionModeSelectAllDrawable"
android:numericShortcut="0"
android:title="@string/action_selection"
android:visible="false"
app:showAsAction="always">
<menu>
<item
android:id="@+id/action_select_all"
android:alphabeticShortcut="a"
android:numericShortcut="1"
android:title="@android:string/selectAll"
app:showAsAction="always"/>
<item
android:id="@+id/action_cut"
android:alphabeticShortcut="t"
android:numericShortcut="2"
android:title="@android:string/cut"
app:showAsAction="always"/>
<item
android:id="@+id/action_copy"
android:alphabeticShortcut="c"
android:numericShortcut="3"
android:title="@android:string/copy"
app:showAsAction="always"/>
<item
android:id="@+id/action_delete"
android:alphabeticShortcut="d"
android:numericShortcut="4"
android:title="@string/delete"
app:showAsAction="always"/>
</menu>
</item>
<item android:title="@string/action_add_rule"
android:icon="@drawable/ic_av_playlist_add"
android:alphabeticShortcut="a"
......
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:ignore="AppCompatResource">
<item
android:id="@+id/action_select_all"
android:alphabeticShortcut="a"
android:icon="?attr/actionModeSelectAllDrawable"
android:numericShortcut="1"
android:title="@android:string/selectAll"
android:showAsAction="always"/>
<item
android:id="@+id/action_cut"
android:alphabeticShortcut="t"
android:icon="?attr/actionModeCutDrawable"
android:numericShortcut="2"
android:title="@android:string/cut"
android:showAsAction="always"/>
<item
android:id="@+id/action_copy"
android:alphabeticShortcut="c"
android:icon="?attr/actionModeCopyDrawable"
android:numericShortcut="3"
android:title="@android:string/copy"
android:showAsAction="always"/>
<item
android:id="@+id/action_delete"
android:alphabeticShortcut="d"
android:icon="@drawable/ic_action_delete"
android:numericShortcut="4"
android:title="@string/delete"
android:showAsAction="always"/>
</menu>
......@@ -4,7 +4,7 @@
<!-- misc -->
<string name="profile">"پروفایل"</string>
<string name="connection_test_testing">"در حال آزمایش..."</string>
<string name="connection_test_testing">"در حال آزمایش"</string>
<!-- proxy category -->
<string name="profile_name">"نام پروفایل"</string>
......@@ -20,7 +20,7 @@
<string name="invalid_server">"آدرس سرور نامعتبر است"</string>
<string name="service_failed">"اتصال به سرور ناموفق بود!"</string>
<string name="stop">"غیرفعال‌کردن"</string>
<string name="stopping">"در حال قطع‌کردن اتصال..."</string>
<string name="stopping">"در حال قطع‌کردن اتصال"</string>
<string name="profile_invalid_input">"هیچ پروفایل معتبری یافت نشد!"</string>
<!-- alert category -->
......@@ -33,7 +33,6 @@
<string name="profiles">"پروفایل‌ها"</string>
<string name="settings">"تنظیمات"</string>
<string name="faq">"سوالات مداول"</string>
<string name="recovery">"ریست"</string>
<string name="about">"درباره ما"</string>
<string name="edit">"ویرایش"</string>
<string name="share">"به اشتراک گذاری"</string>
......@@ -52,9 +51,8 @@
<!-- status -->
<string name="sent">"فرستاده‌شده:"</string>
<string name="received">"دریافت‌شده:"</string>
<string name="connecting">"در حال اتصال..."</string>
<string name="connecting">"در حال اتصال"</string>
<string name="vpn_connected">"وصل شد. برای بررسی اتصال ضربه (Tap) بزنید."</string>
<string name="nat_connected">"اتصال برقرار است."</string>
<string name="not_connected">"اتصال برقرار نیست!"</string>
<string name="route_entry_all">"همه"</string>
</resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- misc -->
<string name="profile">"Profil"</string>
<string name="connection_test_error">"Impossible de détecter la connexion Internet :%s"</string>
<string name="connection_test_fail">"Internet Indisponible"</string>
<string name="connection_test_error_status_code">"Code de l'Erreur : #%d"</string>
<string name="proxy">"Serveur"</string>
<string name="sitekey">"Mot de passe"</string>
<string name="service_failed">"Échec de la connexion au serveur distant"</string>
<!-- alert category -->
<string name="close">"Fermer"</string>
<string name="profile_empty">"Veuillez sélectionner un profil"</string>
<!-- menu category -->
<string name="profiles">"Profils"</string>
<string name="settings">"Paramètres"</string>
<string name="about">"À propos"</string>
<string name="share">"Partager"</string>
<string name="add_profile">"Ajouter un Profil"</string>
<string name="action_apply_all">"Appliquer les Paramètres à tous les Profils"</string>
<string name="action_export">"Exporter vers le presse-papiers"</string>
<string name="action_import">"Importer depuis le presse-papiers"</string>
<string name="delete">"Supprimer"</string>
<string name="delete_confirm_prompt">"Êtes-vous sûr de vouloir supprimer ce profil?"</string>
<string name="share_qr_nfc">"QR Code/NFC"</string>
<string name="add_profile_methods_scan_qr_code">"Scanner le QR Code"</string>
<plurals name="removed">
<item quantity="one">"Supprimé"</item>
<item quantity="other">"%d éléments supprimés"</item>
</plurals>
<string name="undo">"Annuler"</string>
</resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">"Shadowsocks"</string>
<string name="quick_toggle">"ON/OFF"</string>
<!-- misc -->
......@@ -9,7 +8,6 @@
<string name="remote_dns">"リモートDNS"</string>
<string name="stat_summary">"送信済み: \t%3$s\t↑\t%1$s/s
受信済み: \t%4$s\t↓\t%2$s/s"</string>
<string name="connection_test_pending">"接続状況確認"</string>
<string name="connection_test_testing">"テスト中…"</string>
<string name="connection_test_available">"成功: %dmsの遅延"</string>
......@@ -35,10 +33,8 @@
<string name="ipv6_summary">"リモートサーバーに IPv6 パケットを転送"</string>
<string name="route_list">"プロキシ方式"</string>
<string name="route_entry_gfwlist">"中国本土からアクセス不可なアドレス以外を迂回する"</string>
<string name="proxied_apps">"アプリ別のプロキシを使用"</string>
<string name="proxied_apps_summary">"アプリ別でプロキシを指定、Android 4.x では必ずNATモードを使用して下さい"</string>
<string name="proxied_apps">"アプリ別のVPNモードを使用"</string>
<string name="proxied_apps_summary">"アプリ別のVPNモードを設定"</string>
<string name="on">"ON"</string>
<string name="bypass_apps">"バイパスモード(迂回モード)"</string>
<string name="bypass_apps_summary">"このモードを選択した場合、指定したアプリのパケットはプロキシを経由しない"</string>
......@@ -74,9 +70,7 @@
<string name="profiles">"プロファイル"</string>
<string name="settings">"オプション設定"</string>
<string name="faq">"よくある質問"</string>
<string name="faq_url">"https://github.com/shadowsocks/shadowsocks-android/blob/master/.github/faq.md"</string>
<string name="about">"本アプリについて"</string>
<string name="about_title">"Shadowsocks %s"</string>
<string name="edit">"編集"</string>
<string name="share">"共有"</string>
<string name="add_profile">"プロファイルを追加"</string>
......@@ -127,10 +121,7 @@
<string name="route_entry_bypass_chn">"中国本土のアドレスを迂回する"</string>
<string name="route_entry_bypass_lan_chn">"LAN 及び中国本土のアドレスを迂回する"</string>
<string name="route_entry_chinalist">"中国本土のアドレス以外を迂回する"</string>
<string name="proxied_apps_summary_v21">"選択したアプリにプロキシを設定する"</string>
<string name="acl_rule_templates_generic">"URL/サブネット/ホスト名 PCRE パターン"</string>
<string name="acl_rule_templates_generic">"サブネットまたはホスト名のPCREパターン"</string>
<string name="acl_rule_templates_domain">"ドメイン及び全てのサブドメイン"</string>
<!-- plugin -->
......@@ -148,7 +139,6 @@
<string name="advanced">"アドバンス"</string>
<string name="service_mode">"サービスモード"</string>
<string name="service_mode_proxy">"プロキシのみ"</string>
<string name="service_mode_vpn">"VPN"</string>
<string name="service_mode_transproxy">"トランスプロキシ"</string>
<string name="port_proxy">"SOCKS5プロキシポート"</string>
<string name="port_local_dns">"ローカルDNSポート"</string>
......@@ -157,4 +147,7 @@
<string name="service_transproxy">"トランスプロキシサービス"</string>
<string name="vpn_permission_denied">"VPNサービス作成のアクセス許可が拒否されました"</string>
<string name="auto_connect_summary_v24">"起動時にShadowsockを有効。 VPN常時接続の使用をお勧めします"</string>
</resources>
<string name="direct_boot_aware">"ダイレクトブート"</string>
<string name="direct_boot_aware_summary">"デバイスがロック解除される前にShadowsocksの自動起動を許可(選択されたプロファイル情報はより少ない保護を受けることになります)"</string>
<string name="acl_rule_online_config">"オンライン設定のURL"</string>
</resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">"Shadowsocks"</string>
<string name="quick_toggle">"켜기/끄기"</string>
<!-- misc -->
......@@ -9,7 +8,9 @@
<string name="remote_dns">"원격 DNS"</string>
<string name="stat_summary">"송신: \t%3$s\t↑\t%1$s/s
수신: \t%4$s\t↓\t%2$s/s"</string>
<plurals name="bytes">
<item quantity="other">"바이트"</item>
</plurals>
<string name="connection_test_pending">"인터넷 연결 상태 검사하기"</string>
<string name="connection_test_testing">"검사 중…"</string>
<string name="connection_test_available">"성공: 지연시간 %dms"</string>
......@@ -34,11 +35,10 @@
<string name="ipv6">"IPv6 라우팅"</string>
<string name="ipv6_summary">"IPv6 트래픽도 원격으로 리다이렉트 합니다"</string>
<string name="route_list">"라우팅 대상"</string>
<string name="route_entry_gfwlist">"GFW List"</string>
<!-- Fuzzy -->
<string name="proxied_apps">"원하는 앱만 프락시 적용하기"</string>
<string name="proxied_apps_summary">"선택한 앱에만 프락시를 적용합니다. Android 4.x 이하에서는 NAT 모드를 써야 합니다."</string>
<string name="proxied_apps_summary">"선택한 앱에만 프락시를 적용합니다"</string>
<string name="on">"활성화"</string>
<string name="bypass_apps">"선택된 앱들만 프록시 적용 제외하기"</string>
<string name="bypass_apps_summary">"이 옵션을 활성화하면 선택된 앱들은 프락시를 사용하지 않습니다"</string>
......@@ -55,7 +55,6 @@
<string name="service_failed">"원격 서버에 접속하는 데 실패했습니다"</string>
<string name="stop">"중지"</string>
<string name="stopping">"종료 중…"</string>
<string name="vpn_error">"%s"</string>
<string name="reboot_required">"VPN 서비스를 시작하는 데 실패했습니다. 장치를 재시작해 보세요."</string>
<string name="profile_invalid_input">"올바른 프로필 데이터를 찾을 수 없습니다"</string>
......@@ -74,13 +73,10 @@
<string name="profiles">"프로필"</string>
<string name="settings">"설정"</string>
<string name="faq">"자주 묻는 질문"</string>
<string name="faq_url">"https://github.com/shadowsocks/shadowsocks-android/blob/master/.github/faq.md"</string>
<string name="about">"이 앱에 대하여"</string>
<string name="about_title">"Shadowsocks %s"</string>
<string name="edit">"수정"</string>
<string name="share">"공유"</string>
<string name="add_profile">"프로필 추가"</string>
<string name="action_apply_all">"Apply Settings to All Profiles"</string>
<string name="action_export">"클립보드로 내보내기"</string>
<string name="action_import">"클립보드에서 불러오기"</string>
<string name="action_export_msg">"성공적으로 내보냈습니다"</string>
......@@ -102,34 +98,15 @@
</plurals>
<string name="undo">"실행 취소"</string>
<!-- tasker -->
<string name="toggle_service_state">"Start the service"</string>
<string name="start_service_default">"Connect to the current server"</string>
<string name="start_service">"Connect to %s"</string>
<string name="stop_service">"Switch to %s"</string>
<string name="profile_default">"Use the current profile"</string>
<!-- status -->
<string name="sent">"송신:"</string>
<string name="received">"수신:"</string>
<string name="connecting">"연결 중…"</string>
<string name="vpn_connected">"연결 완료. 탭 하면 연결 상태를 검사합니다."</string>
<string name="not_connected">"Not connected"</string>
<!-- acl -->
<string name="custom_rules">"사용자 정의 규칙"</string>
<string name="action_selection">"Selection…"</string>
<string name="action_add_rule">"Add rule(s)…"</string>
<string name="edit_rule">"Edit rule"</string>
<string name="route_entry_all">"All"</string>
<string name="route_entry_bypass_lan">"Bypass LAN"</string>
<string name="route_entry_bypass_chn">"Bypass mainland China"</string>
<string name="route_entry_bypass_lan_chn">"Bypass LAN &amp; mainland China"</string>
<string name="route_entry_chinalist">"China List"</string>
<string name="proxied_apps_summary_v21">"Configure VPN mode for selected apps"</string>
<string name="acl_rule_templates_generic">"URL, 서브넷 혹은 호스트 이름 PCRE 패턴"</string>
<string name="acl_rule_templates_domain">"Domain name and all its subdomain names"</string>
<string name="acl_rule_templates_generic">"서브넷 혹은 호스트 이름 PCRE 패턴"</string>
<!-- plugin -->
<string name="plugin">"플러그인"</string>
......@@ -139,21 +116,4 @@
<string name="plugin_untrusted">"경고: 이 플러그인은 신뢰할 수 있는 출처에서 온 것이 아닌 것 같습니다"</string>
<string name="profile_plugin">"플러그인: %s"</string>
<string name="add_profile_scanner_permission_required">"QR 코드를 읽어 들이려면 카메라 권한이 필요합니다"</string>
<!-- notification category -->
<string name="service_vpn">"VPN Service"</string>
<string name="add_profile_methods_manual_settings">"Manual Settings"</string>
<string name="advanced">"Advanced"</string>
<string name="service_mode">"Service mode"</string>
<string name="service_mode_proxy">"Proxy only"</string>
<string name="service_mode_vpn">"VPN"</string>
<string name="service_mode_transproxy">"Transproxy"</string>
<string name="port_proxy">"SOCKS5 proxy port"</string>
<string name="port_local_dns">"Local DNS port"</string>
<string name="port_transproxy">"Transproxy port"</string>
<string name="service_proxy">"Proxy Service"</string>
<string name="service_transproxy">"Transproxy Service"</string>
<string name="vpn_permission_denied">"Permission denied to create a VPN service"</string>
<string name="auto_connect_summary_v24">"Enable Shadowsocks on startup. Recommended to use always-on VPN
instead"</string>
</resources>
</resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">"Shadowsocks"</string>
<string name="quick_toggle">"Подключение"</string>
<!-- misc -->
<string name="profile">"Профиль"</string>
<string name="profile_summary">"Переключить на другой профиль или добавить новые"</string>
<string name="remote_dns">"Удалённый DNS"</string>
<string name="stat_summary">"Отправлено: \t%3$s\t↑\t%1$s/s
Получено: \t%4$s\t↓\t%2$s/s"</string>
<string name="stat_summary">"Отправлено: \t%3$s\t↑\t%1$s
Получено: \t\t\t%4$s\t↓\t%2$s"</string>
<plurals name="bytes">
<item quantity="one">"байт"</item>
<item quantity="few">"байта"</item>
......@@ -27,11 +25,11 @@
<string name="proxy_cat">"Настройки Сервера"</string>
<!-- proxy category -->
<string name="profile_name">"Имя Профиля"</string>
<string name="profile_name">"Имя профиля"</string>
<string name="proxy">"Сервер"</string>
<string name="remote_port">"Удалённый порт"</string>
<string name="sitekey">"Пароль"</string>
<string name="enc_method">"Метод Шифрования"</string>
<string name="enc_method">"Метод шифрования"</string>
<!-- feature category -->
<string name="feature_cat">"Дополнительные Настройки"</string>
......@@ -41,14 +39,12 @@
<string name="ipv6_summary">"Перенаправлять трафик IPv6 на удалённый сервер"</string>
<string name="route_list">"Маршрут"</string>
<string name="route_entry_gfwlist">"Список GFW"</string>
<string name="proxied_apps">"Прокси для выбранных приложений"</string>
<string name="proxied_apps_summary">"Установить прокси для выбранных приложений, требует режим NAT под Android 4.x"</string>
<string name="proxied_apps">"Режим VPN для выбранных приложений"</string>
<string name="proxied_apps_summary">"Выбрать приложения, для которых нужно использовать режим VPN"</string>
<string name="on">"Вкл"</string>
<string name="bypass_apps">"В обход прокси"</string>
<string name="bypass_apps_summary">"Включите эту опцию для работы выбранных приложений в обход прокси"</string>
<string name="auto_connect">"Авто Подключение"</string>
<string name="auto_connect">"Авто-подключение"</string>
<string name="auto_connect_summary">"Запускать Shadowsocks при включении"</string>
<string name="tcp_fastopen_summary">"Переключение требует наличия ROOT прав"</string>
<string name="tcp_fastopen_summary_unsupported">"Неподдерживаемая версия ядра: %s &lt; 3.7.1"</string>
......@@ -61,9 +57,8 @@
<string name="service_failed">"Ошибка при подключении к удалённому серверу"</string>
<string name="stop">"Остановить"</string>
<string name="stopping">"Останавливается…"</string>
<string name="vpn_error">"%s"</string>
<string name="reboot_required">"Не удалось запустить службу VPN. Возможно, требуется перезагрузить ваше устройство."</string>
<string name="profile_invalid_input">"No valid profile data found."</string>
<string name="profile_invalid_input">"Не найдено действительных данных профиля."</string>
<!-- alert category -->
<string name="yes">"Да"</string>
......@@ -79,13 +74,13 @@
<!-- menu category -->
<string name="profiles">"Профили"</string>
<string name="settings">"Настройки"</string>
<string name="faq">"FAQ"</string>
<string name="faq_url">"https://github.com/shadowsocks/shadowsocks-android/blob/master/.github/faq.md"</string>
<string name="faq">"ЧаВо"</string>
<string name="faq_url">"https://github.com/shadowsocks/shadowsocks-android/blob/master/.github/faq.ru.md"</string>
<string name="about">"О приложении"</string>
<string name="about_title">"Shadowsocks %s"</string>
<string name="edit">"Изменить"</string>
<string name="share">"Поделиться"</string>
<string name="add_profile">"Добавить Профиль"</string>
<string name="add_profile">"Добавить профиль"</string>
<string name="action_apply_all">"Применить настройки для всех профилей"</string>
<string name="action_export">"Экспортировать в буфер обмена"</string>
<string name="action_import">"Импортировать из буфера обмена"</string>
......@@ -101,7 +96,7 @@
<string name="delete">"Удалить"</string>
<string name="delete_confirm_prompt">"Вы уверены, что хотите удалить этот профиль?"</string>
<string name="share_qr_nfc">"QR-код/NFC"</string>
<string name="add_profile_dialog">"Добавить этот Профиль Shadowsocks?"</string>
<string name="add_profile_dialog">"Добавить этот профиль Shadowsocks?"</string>
<string name="add_profile_methods_scan_qr_code">"Сканировать QR-код"</string>
<plurals name="removed">
<item quantity="one">"Удалено %d элементов"</item>
......@@ -135,10 +130,7 @@
<string name="route_entry_bypass_chn">"Все, кроме Китая"</string>
<string name="route_entry_bypass_lan_chn">"Все, кроме LAN и Китая"</string>
<string name="route_entry_chinalist">"Список Китай"</string>
<string name="proxied_apps_summary_v21">"Установить прокси для выбранных приложений"</string>
<string name="acl_rule_templates_generic">"URL/Подсеть/Регулярное выражение (PCRE) имени хоста"</string>
<string name="acl_rule_templates_generic">"Подсеть или имя хоста, записанное регулярным выражением (PCRE)"</string>
<string name="acl_rule_templates_domain">"Доменное имя и все его поддомены"</string>
<!-- plugin -->
......@@ -151,19 +143,21 @@
<string name="add_profile_scanner_permission_required">"Разрешение камеры требуется для сканирования QR код."</string>
<!-- notification category -->
<string name="service_vpn">"VPN Service"</string>
<string name="add_profile_methods_manual_settings">"Manual Settings"</string>
<string name="advanced">"Advanced"</string>
<string name="service_mode">"Service mode"</string>
<string name="service_mode_proxy">"Proxy only"</string>
<string name="service_mode_vpn">"VPN"</string>
<string name="service_mode_transproxy">"Transproxy"</string>
<string name="port_proxy">"SOCKS5 proxy port"</string>
<string name="port_local_dns">"Local DNS port"</string>
<string name="port_transproxy">"Transproxy port"</string>
<string name="service_proxy">"Proxy Service"</string>
<string name="service_transproxy">"Transproxy Service"</string>
<string name="vpn_permission_denied">"Permission denied to create a VPN service"</string>
<string name="auto_connect_summary_v24">"Enable Shadowsocks on startup. Recommended to use always-on VPN
instead"</string>
</resources>
<string name="service_vpn">"Служба VPN"</string>
<string name="add_profile_methods_manual_settings">"Ручные настройки"</string>
<string name="advanced">"Дополнительно"</string>
<string name="service_mode">"Режим"</string>
<string name="service_mode_proxy">"Только прокси"</string>
<string name="service_mode_transproxy">"Прозрачный прокси"</string>
<string name="port_proxy">"Порт SOCKS5 прокси"</string>
<string name="port_local_dns">"Локальный порт DNS"</string>
<string name="port_transproxy">"Порт прозрачного прокси"</string>
<string name="service_proxy">"Служба прокси"</string>
<string name="service_transproxy">"Служба прозрачный прокси"</string>
<string name="vpn_permission_denied">"Нет разрешения на создание VPN соединения"</string>
<string name="auto_connect_summary_v24">"Включить Shadowsocks во время запуска. Лучше использовать режим постоянной VPN."</string>
<string name="speed">"%s/с"</string>
<string name="direct_boot_aware">"Непосредственная загрузка"</string>
<string name="direct_boot_aware_summary">"Позволить Shadowsocks запускаться до того, как устройство будет разблокировано (данные выбранного профиля будут хуже защищены)"</string>
<string name="acl_rule_online_config">"URL конфигурации"</string>
</resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="fab_margin_top">-28dp</dimen>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">"影梭"</string>
<string name="quick_toggle">"开关"</string>
<!-- misc -->
<string name="profile">"配置文件"</string>
<string name="profile_summary">"新建或切换到其他配置文件"</string>
<string name="remote_dns">"远程 DNS"</string>
<string name="stat_summary">"上传: \t%3$s\t↑\t%1$s/s
下载: \t%4$s\t↓\t%2$s/s"</string>
<string name="stat_summary">"上传: \t%3$s\t↑\t%1$s
下载: \t%4$s\t↓\t%2$s"</string>
<string name="connection_test_pending">"检查网络连接"</string>
<string name="connection_test_testing">"测试中…"</string>
<string name="connection_test_available">"连接成功:延时 %d 毫秒"</string>
<string name="connection_test_error">"失败:%s"</string>
<string name="connection_test_fail">"无互联网连接"</string>
<string name="connection_test_error_status_code">"状态码无效(#%d)"</string>
<!-- proxy category -->
......@@ -29,7 +21,6 @@
<!-- proxy category -->
<string name="profile_name">"配置名称"</string>
<string name="proxy">"服务器"</string>
<string name="remote_port">"远程端口"</string>
<string name="sitekey">"密码"</string>
......@@ -44,7 +35,6 @@
<string name="route_list">"路由"</string>
<string name="route_entry_gfwlist">"GFW 列表"</string>
<string name="proxied_apps">"分应用 VPN"</string>
<string name="proxied_apps_summary">"允许部分应用绕过 VPN"</string>
<string name="on">"启用"</string>
<string name="bypass_apps">"绕行模式"</string>
......@@ -81,7 +71,6 @@
<string name="profiles">"配置文件"</string>
<string name="settings">"设置选项"</string>
<string name="faq">"常见问题"</string>
<string name="faq_url">"https://github.com/shadowsocks/shadowsocks-android/blob/master/.github/faq.md"</string>
<string name="about">"关于"</string>
<string name="about_title">"影梭 (Shadowsocks) %s"</string>
<string name="edit">"编辑"</string>
......@@ -133,12 +122,8 @@
<string name="route_entry_bypass_chn">"绕过中国大陆地址"</string>
<string name="route_entry_bypass_lan_chn">"绕过局域网及中国大陆地址"</string>
<string name="route_entry_chinalist">"仅代理中国大陆地址"</string>
<string name="proxied_apps_summary_v21">"为应用程序分别设置代理"</string>
<string name="acl_rule_templates_generic">"URL/子网/域名 PCRE 正则表达式"</string>
<string name="acl_rule_templates_generic">"子网/域名 PCRE 正则表达式"</string>
<string name="acl_rule_templates_domain">"域名及其子域名"</string>
<string name="acl_rule_online_config">配置文件地址</string>
<!-- plugin -->
<string name="plugin">"插件"</string>
......@@ -155,7 +140,6 @@
<string name="advanced">"高级选项"</string>
<string name="service_mode">"服务模式"</string>
<string name="service_mode_proxy">"仅代理"</string>
<string name="service_mode_vpn">"VPN"</string>
<string name="service_mode_transproxy">"透明代理"</string>
<string name="port_proxy">"SOCKS5 代理端口"</string>
<string name="port_local_dns">"本地 DNS 端口"</string>
......@@ -164,4 +148,7 @@
<string name="service_transproxy">"透明代理模式"</string>
<string name="vpn_permission_denied">"创建 VPN 服务权限不足"</string>
<string name="auto_connect_summary_v24">"允许 Shadowsocks 随系统启动,建议使用始终开启的 VPN"</string>
</resources>
<string name="direct_boot_aware">"直接启动"</string>
<string name="direct_boot_aware_summary">"允许在设备解锁前启动服务(选中的配置信息会不那么安全)"</string>
<string name="acl_rule_online_config">"在线规则文件 URL"</string>
</resources>
\ No newline at end of file
......@@ -37,10 +37,8 @@
<string name="ipv6_summary">"向遠端重新導向 IPv6 流量"</string>
<string name="route_list">"路由"</string>
<string name="route_entry_gfwlist">"GFW List"</string>
<string name="proxied_apps">"個別應用程式的 Proxy"</string>
<string name="proxied_apps_summary">"為已選擇的應用程式設定 Proxy,在 Android 4.X 以下需要開啟 NAT 模式"</string>
<string name="proxied_apps">"個別應用程式的 VPN"</string>
<string name="proxied_apps_summary">"為已選擇的應用程式設定 VPN"</string>
<string name="on">"開"</string>
<string name="bypass_apps">"略過模式"</string>
<string name="bypass_apps_summary">"啟用此選項,會略過已選擇的應用程式"</string>
......@@ -57,7 +55,6 @@
<string name="service_failed">"連線至遠端伺服器失敗"</string>
<string name="stop">"停止"</string>
<string name="stopping">"關閉中…"</string>
<string name="vpn_error">"%s"</string>
<string name="reboot_required">"VPN 服務啟動失敗。您或許需要重新啟動您的裝置。"</string>
<string name="profile_invalid_input">"未找到有效的設定檔資料。"</string>
......@@ -76,7 +73,6 @@
<string name="profiles">"設定檔"</string>
<string name="settings">"設定"</string>
<string name="faq">"常見問題"</string>
<string name="faq_url">"https://github.com/shadowsocks/shadowsocks-android/blob/master/.github/faq.md"</string>
<string name="about">"關於"</string>
<string name="about_title">"Shadowsocks %s"</string>
<string name="edit">"編輯"</string>
......@@ -128,12 +124,8 @@
<string name="route_entry_bypass_chn">"略過中國大陸"</string>
<string name="route_entry_bypass_lan_chn">"略過區域網路及中國大陸"</string>
<string name="route_entry_chinalist">"China List"</string>
<string name="proxied_apps_summary_v21">"為已選擇的應用程式設定 Proxy"</string>
<string name="acl_rule_templates_generic">"URL/子網路/主機名稱 PCRE 模式"</string>
<string name="acl_rule_templates_generic">"子網路/主機名稱 PCRE 模式"</string>
<string name="acl_rule_templates_domain">"網域及其所有子網域"</string>
<string name="acl_rule_online_config">配置文件地址</string>
<!-- plugin -->
<string name="plugin">"外掛程式"</string>
......@@ -150,7 +142,6 @@
<string name="advanced">"高級"</string>
<string name="service_mode">"服務模式"</string>
<string name="service_mode_proxy">"仅代理"</string>
<string name="service_mode_vpn">"VPN"</string>
<string name="service_mode_transproxy">"透明代理"</string>
<string name="port_proxy">"SOCKS5 代理連接埠"</string>
<string name="port_local_dns">"本地 DNS 連接埠"</string>
......@@ -159,4 +150,4 @@
<string name="service_transproxy">"透明代理服務"</string>
<string name="vpn_permission_denied">"没有權限創建 VPN 服務"</string>
<string name="auto_connect_summary_v24">"允許 Shadowsocks 隨系統啟動,建議使用始終開啟的 VPN"</string>
</resources>
</resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="profile_item_max_width">480dp</dimen>
<dimen name="qr_code_size">250dp</dimen>
<dimen name="profile_padding">8dp</dimen>
<dimen name="bottom_sheet_padding">8dp</dimen>
<dimen name="fab_margin_top">-44dp</dimen>
</resources>
......@@ -13,6 +13,7 @@
<AutoSummaryEditTextPreference
android:key="proxy"
app:pref_summaryHasText="%s"
android:singleLine="true"
android:title="@string/proxy"/>
<AutoSummaryEditTextPreference
android:key="remotePortNum"
......@@ -25,6 +26,7 @@
app:pref_summaryPasswordSubstituteLength="0"
android:key="sitekey"
app:pref_summaryHasText="%s"
android:singleLine="true"
android:title="@string/sitekey"/>
<SimpleMenuPreference
android:key="encMethod"
......
......@@ -24,7 +24,6 @@ android {
}
dependencies {
api "com.android.support:preference-v7:$supportLibraryVersion"
api "com.android.support:preference-v14:$supportLibraryVersion"
api "com.takisoft.fix:preference-v7:$takisoftFixVersion"
api "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlinVersion"
......
......@@ -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">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
......
......@@ -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" />
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- proxy category -->
<string name="proxy_cat">"サーバー設定"</string>
<!-- feature category -->
<string name="feature_cat">"ファンクション設定"</string>
<!-- alert category -->
<string name="feature_cat">"機能設定"</string>
<string name="unsaved_changes_prompt">"変更は保存されておりません、保存しますか?"</string>
<string name="yes">"はい"</string>
<string name="no">"いいえ"</string>
<string name="unsaved_changes_prompt">"変更は適応されておりません、保存しますか?"</string>
<string name="apply">"適用"</string>
<string name="apply">"適応"</string>
</resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- proxy category -->
<string name="proxy_cat">"Настройки Сервера"</string>
<!-- feature category -->
<string name="feature_cat">"Дополнительные Настройки"</string>
<!-- alert category -->
<string name="proxy_cat">"Настройки сервера"</string>
<string name="feature_cat">"Настройки опций"</string>
<string name="unsaved_changes_prompt">"Сохранить изменения?"</string>
<string name="yes">"Да"</string>
<string name="no">"Нет"</string>
<string name="unsaved_changes_prompt">"Изменения не сохранены. Сохранить?"</string>
<string name="apply">"Применить"</string>
</resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- proxy category -->
<string name="proxy_cat">"服务器设置"</string>
<!-- feature category -->
<string name="feature_cat">"功能设置"</string>
<!-- alert category -->
<string name="unsaved_changes_prompt">"是否要保存修改?"</string>
<string name="yes">"是"</string>
<string name="no">"否"</string>
<string name="unsaved_changes_prompt">"保存修改吗?"</string>
<string name="apply">"应用"</string>
</resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.Shadowsocks" parent="PreferenceFixTheme.Light.NoActionBar">
<!--suppress NewApi -->
<item name="android:navigationBarColor">@color/material_primary_700</item>
<item name="actionModeCloseDrawable">@drawable/ic_navigation_close</item>
<item name="colorAccent">@color/material_accent_200</item>
<item name="colorButtonNormal">@color/material_accent_200</item>
<item name="colorPrimary">@color/material_primary_500</item>
<item name="colorPrimaryDark">@color/material_primary_700</item>
<item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material.Fix</item>
<item name="preferenceCategory_marginBottom">8dp</item>
<item name="windowActionModeOverlay">true</item>
</style>
</resources>
#!/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
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