Unverified Commit 343ecac8 authored by Max Lv's avatar Max Lv Committed by GitHub

Merge branch 'master' into fix-bab

parents 0cee695f e2662ce9
......@@ -6,17 +6,16 @@ jobs:
- image: shadowsocks/shadowsocks-android:circleci
environment:
JVM_OPTS: -Xmx3.5g
GRADLE_OPTS: -Dorg.gradle.workers.max=1 -Dorg.gradle.daemon=false -Dkotlin.compiler.execution.strategy="in-process"
steps:
- checkout
- run: git submodule update --init --recursive
- run: pushd core/src/overture; ./make.bash 21; popd
- restore_cache:
key: jars-{{ checksum "build.gradle" }}
- run:
name: Run Build
command: ./gradlew assembleDebug
- run:
name: Run Tests
command: ./gradlew check
name: Run Build and Tests
command: ./gradlew assembleDebug check
- save_cache:
paths:
- ~/.gradle
......
sudo: required
addons:
artifacts: true
services:
- docker
before_install:
- docker pull shadowsocks/shadowsocks-android:circleci
- chmod -R a+wrx .
script:
- docker run --rm -v ${PWD}:/build shadowsocks/shadowsocks-android:circleci bash -c "cd /build; ./gradlew assembleDebug check"
## Shadowsocks for Android
[![CircleCI](https://circleci.com/gh/shadowsocks/shadowsocks-android.svg?style=svg)](https://circleci.com/gh/shadowsocks/shadowsocks-android)
[![Build Status](https://travis-ci.org/shadowsocks/shadowsocks-android.svg?branch=master)](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)
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/1a21d48d466644cdbcb57a1889abea5b)](https://www.codacy.com/app/shadowsocks/shadowsocks-android?utm_source=github.com&utm_medium=referral&utm_content=shadowsocks/shadowsocks-android&utm_campaign=Badge_Grade)
......@@ -28,6 +28,14 @@ but probably painful. Further contributions regarding building on Windows are al
* Clone the repo using `git clone --recurse-submodules <repo>` or update submodules using `git submodule update --init --recursive`
* Build it using Android Studio or gradle script
### BUILD WITH DOCKER
```bash
mkdir build
sudo chown 3434:3434 build
docker run --rm -v ${PWD}/build:/build shadowsocks/shadowsocks-android:circleci bash -c "cd /build; git clone https://github.com/shadowsocks/shadowsocks-android; cd shadowsocks-android; git submodule update --init --recursive; ./gradlew assembleDebug"
```
### [TRANSLATE](https://discourse.shadowsocks.org/t/poeditor-translation-main-thread/30)
## OPEN SOURCE LICENSES
......
......@@ -4,14 +4,13 @@ apply plugin: 'com.github.ben-manes.versions'
buildscript {
ext {
kotlinVersion = '1.3.0'
kotlinVersion = '1.3.10'
minSdkVersion = 21
sdkVersion = 28
compileSdkVersion = 28
buildToolsVersion = '28.0.3'
androidxVersion = '1.0.0'
roomVersion = '2.0.0'
workVersion = '1.0.0-alpha10'
workVersion = '1.0.0-alpha11'
preferencexVersion = '1.0.0'
junitVersion = '4.12'
androidTestVersion = '1.1.0'
......
......@@ -71,14 +71,14 @@ tasks.whenTaskAdded { task ->
dependencies {
api project(':plugin')
api "android.arch.work:work-runtime-ktx:$workVersion"
api "androidx.preference:preference:$androidxVersion"
api "androidx.preference:preference:1.0.0"
api "androidx.room:room-runtime:$roomVersion"
api 'com.crashlytics.sdk.android:crashlytics:2.9.5'
api 'com.crashlytics.sdk.android:crashlytics:2.9.6'
api 'com.google.firebase:firebase-config:16.0.0'
api 'com.google.firebase:firebase-core:16.0.0'
api 'com.squareup.okhttp3:okhttp:3.11.0'
api 'com.squareup.okhttp3:okhttp:3.12.0'
api "com.takisoft.preferencex:preferencex:$preferencexVersion"
api 'eu.chainfire:libsuperuser:1.0.0.201704021214'
api 'eu.chainfire:libsuperuser:1.0.0.201811281328'
kapt "androidx.room:room-compiler:$roomVersion"
testImplementation "androidx.room:room-testing:$roomVersion"
testImplementation "junit:junit:$junitVersion"
......
......@@ -116,7 +116,7 @@ object Core {
for (dir in arrayOf("acl", "overture"))
try {
for (file in assetManager.list(dir)!!) assetManager.open("$dir/$file").use { input ->
File(deviceStorage.filesDir, file).outputStream().use { output -> input.copyTo(output) }
File(deviceStorage.noBackupFilesDir, file).outputStream().use { output -> input.copyTo(output) }
}
} catch (e: IOException) {
printLog(e)
......
......@@ -38,39 +38,17 @@
package com.github.shadowsocks;
import android.os.Build;
import android.system.ErrnoException;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import android.system.ErrnoException;
public class JniHelper {
static {
System.loadLibrary("jni-helper");
}
@Deprecated // Use Process.destroy() since API 24
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 new ErrnoException("kill", errno);
}
@Deprecated // only implemented for before API 24
public static boolean waitForCompat(@NonNull Process process, long millis) throws Exception {
if (Build.VERSION.SDK_INT >= 24) throw new UnsupportedOperationException("Never call this method in OpenJDK!");
final Object mutex = getExitValueMutex(process);
synchronized (mutex) {
if (getExitValue(process) == null) mutex.wait(millis);
return getExitValue(process) != null;
}
}
public static native int sigkill(int pid);
private static native int sigterm(Process process);
private static native Integer getExitValue(Process process);
private static native Object getExitValueMutex(Process process);
public static native int sendFd(int fd, @NonNull String path);
public static native void close(int fd);
public static native void sendFd(int fd, @NonNull String path) throws ErrnoException;
@Nullable
public static native byte[] parseNumericAddress(@NonNull String str);
}
......@@ -46,7 +46,7 @@ class Acl {
val networkAclParser = "^IMPORT_URL\\s*<(.+)>\\s*$".toRegex()
fun getFile(id: String, context: Context = Core.deviceStorage) = File(context.filesDir, "$id.acl")
fun getFile(id: String, context: Context = Core.deviceStorage) = File(context.noBackupFilesDir, "$id.acl")
var customRules: Acl
get() {
......@@ -148,7 +148,7 @@ class Acl {
}
fun fromId(id: String): Acl = try {
fromReader(Acl.getFile(id).bufferedReader())
fromReader(getFile(id).bufferedReader())
} catch (_: IOException) { this }
fun flatten(depth: Int): Acl {
......
......@@ -20,18 +20,14 @@
package com.github.shadowsocks.bg
import android.annotation.TargetApi
import android.app.Service
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Build
import android.os.IBinder
import android.os.RemoteCallbackList
import android.os.SystemClock
import android.os.*
import android.util.Base64
import android.util.Log
import androidx.core.os.UserManagerCompat
import androidx.core.content.getSystemService
import androidx.core.os.bundleOf
import com.crashlytics.android.Crashlytics
import com.github.shadowsocks.Core
......@@ -195,9 +191,8 @@ object BaseService {
.put("plugin_opts", plugin.toString())
}
// sensitive Shadowsocks config is stored in
return File(if (UserManagerCompat.isUserUnlocked(app)) app.filesDir else @TargetApi(24) {
Core.deviceStorage.noBackupFilesDir // only API 24+ will be in locked state
}, CONFIG_FILE).apply {
return File((if (Build.VERSION.SDK_INT < 24 || app.getSystemService<UserManager>()?.isUserUnlocked != true)
app else Core.deviceStorage).noBackupFilesDir, CONFIG_FILE).apply {
shadowsocksConfigFile = this
writeText(config.toString())
}
......
......@@ -20,11 +20,12 @@
package com.github.shadowsocks.bg
import android.system.ErrnoException
import android.system.Os
import android.text.TextUtils
import android.util.Log
import com.crashlytics.android.Crashlytics
import com.github.shadowsocks.Core.app
import com.github.shadowsocks.JniHelper
import java.io.File
import java.io.FileNotFoundException
......@@ -44,11 +45,13 @@ object Executable {
} catch (ignore: FileNotFoundException) {
continue
}.split(Character.MIN_VALUE, limit = 2).first())
if (exe.parent == app.applicationInfo.nativeLibraryDir && EXECUTABLES.contains(exe.name)) {
val errno = JniHelper.sigkill(process.name.toInt())
if (errno != 0) {
Crashlytics.log(Log.WARN, "kill",
"SIGKILL ${exe.absolutePath} (${process.name}) failed with $errno")
if (exe.parent == app.applicationInfo.nativeLibraryDir && EXECUTABLES.contains(exe.name)) try {
Os.kill(process.name.toInt(), 9) // SIGKILL
} catch (e: ErrnoException) {
if (e.errno != 3) { // ESRCH
e.printStackTrace()
Crashlytics.log(Log.WARN, "kill", "SIGKILL ${exe.absolutePath} (${process.name}) failed")
Crashlytics.logException(e)
}
}
}
......
......@@ -22,10 +22,11 @@ package com.github.shadowsocks.bg
import android.os.Build
import android.os.SystemClock
import android.system.ErrnoException
import android.system.Os
import android.util.Log
import com.crashlytics.android.Crashlytics
import com.github.shadowsocks.Core
import com.github.shadowsocks.JniHelper
import com.github.shadowsocks.utils.Commandline
import com.github.shadowsocks.utils.thread
import java.io.File
......@@ -37,6 +38,9 @@ import java.util.concurrent.atomic.AtomicReference
class GuardedProcessPool {
companion object Dummy : IOException("Oopsie the developer has made a no-no") {
private const val TAG = "GuardedProcessPool"
private val ProcessImpl by lazy { Class.forName("java/lang/ProcessManager\$ProcessImpl") }
private val pid by lazy { ProcessImpl.getField("pid").apply { isAccessible = true } }
private val exitValueMutex by lazy { ProcessImpl.getField("exitValueMutex").apply { isAccessible = true } }
}
private inner class Guard(private val cmd: List<String>, private val onRestartCallback: (() -> Unit)?) {
......@@ -66,7 +70,7 @@ class GuardedProcessPool {
process = ProcessBuilder(cmd)
.redirectErrorStream(true)
.directory(Core.deviceStorage.filesDir)
.directory(Core.deviceStorage.noBackupFilesDir)
.start()
streamLogger(process.inputStream, Log::i)
......@@ -88,9 +92,21 @@ class GuardedProcessPool {
pushException(e)
} finally {
if (process != null) {
if (Build.VERSION.SDK_INT < 24) @Suppress("DEPRECATION") {
JniHelper.sigtermCompat(process)
JniHelper.waitForCompat(process, 500)
if (Build.VERSION.SDK_INT < 24) {
val pid = pid.get(process) as Int
try {
Os.kill(pid, 15) // SIGTERM
} catch (e: ErrnoException) {
if (e.errno != 3) throw e // ESRCH
}
val mutex = exitValueMutex.get(process) as Object
synchronized(mutex) {
try {
process.exitValue()
} catch (e: IllegalThreadStateException) {
mutex.wait(500)
}
}
}
process.destroy()
process.waitFor() // ensure the process is destroyed
......
......@@ -52,7 +52,7 @@ object LocalDnsService {
}
fun buildOvertureConfig(file: String) = file.also {
File(Core.deviceStorage.filesDir, it).writeText(JSONObject().run {
File(Core.deviceStorage.noBackupFilesDir, it).writeText(JSONObject().run {
put("BindAddress", "${DataStore.listenAddress}:${DataStore.portLocalDns}")
put("RedirectIPv6Record", true)
put("DomainBase64Decode", false)
......
......@@ -29,7 +29,7 @@ import java.nio.ByteBuffer
import java.nio.ByteOrder
class TrafficMonitorThread : LocalSocketListener("TrafficMonitorThread") {
override val socketFile = File(Core.deviceStorage.filesDir, "stat_path")
override val socketFile = File(Core.deviceStorage.noBackupFilesDir, "stat_path")
override fun accept(socket: LocalSocket) {
try {
......
......@@ -52,7 +52,7 @@ class TransproxyService : Service(), LocalDnsService.Interface {
}
private fun startRedsocksDaemon() {
File(Core.deviceStorage.filesDir, "redsocks.conf").writeText("""base {
File(Core.deviceStorage.noBackupFilesDir, "redsocks.conf").writeText("""base {
log_debug = off;
log_info = off;
log = stderr;
......
......@@ -27,6 +27,8 @@ import android.content.pm.PackageManager
import android.net.*
import android.os.Build
import android.os.ParcelFileDescriptor
import android.system.ErrnoException
import android.system.Os
import androidx.core.content.getSystemService
import com.github.shadowsocks.Core
import com.github.shadowsocks.JniHelper
......@@ -72,21 +74,24 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
}
private inner class ProtectWorker : LocalSocketListener("ShadowsocksVpnThread") {
override val socketFile: File = File(Core.deviceStorage.filesDir, "protect_path")
override val socketFile: File = File(Core.deviceStorage.noBackupFilesDir, "protect_path")
override fun accept(socket: LocalSocket) {
try {
socket.inputStream.read()
val fd = socket.ancillaryFileDescriptors!!.single()!!
val fdInt = getInt.invoke(fd) as Int
socket.outputStream.write(if (try {
val network = underlyingNetwork
if (network != null && Build.VERSION.SDK_INT >= 23) {
network.bindSocket(fd)
true
} else protect(fdInt)
} else protect(getInt.invoke(fd) as Int)
} finally {
JniHelper.close(fdInt) // Trick to close file decriptor
try {
Os.close(fd)
} catch (e: ErrnoException) {
printLog(e)
}
}) 0 else 1)
} catch (e: IOException) {
printLog(e)
......@@ -164,8 +169,7 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
super.startNativeProcesses()
val fd = startVpn()
if (!sendFd(fd)) throw IOException("sendFd failed")
sendFd(startVpn())
}
override fun buildAdditionalArguments(cmd: ArrayList<String>): ArrayList<String> {
......@@ -242,19 +246,27 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
cmd += "--dnsgw"
cmd += "127.0.0.1:${DataStore.portLocalDns}"
}
data.processes.start(cmd) { sendFd(fd) }
data.processes.start(cmd) {
try {
sendFd(fd)
} catch (e: ErrnoException) {
stopRunner(true, e.message)
}
}
return fd
}
private fun sendFd(fd: Int): Boolean {
if (fd != -1) {
var tries = 0
while (tries < 10) {
Thread.sleep(30L shl tries)
if (JniHelper.sendFd(fd, File(Core.deviceStorage.filesDir, "sock_path").absolutePath) != -1) return true
tries += 1
}
private fun sendFd(fd: Int) {
if (fd == -1) throw IOException("Invalid fd (-1)")
var tries = 0
val path = File(Core.deviceStorage.noBackupFilesDir, "sock_path").absolutePath
while (true) try {
Thread.sleep(30L shl tries)
JniHelper.sendFd(fd, path)
return
} catch (e: ErrnoException) {
if (tries >= 10) throw e
tries += 1
}
return false
}
}
......@@ -44,7 +44,7 @@ class Profile : Serializable {
companion object {
private const val TAG = "ShadowParser"
private const val serialVersionUID = 0L
private val pattern = """(?i)ss://[-a-zA-Z0-9+&@#/%?=~_|!:,.;\[\]]*[-a-zA-Z0-9+&@#/%=~_|\[\]]""".toRegex()
private val pattern = """(?i)ss://[-a-zA-Z0-9+&@#/%?=.~*'()|!:,.;\[\]]*[-a-zA-Z0-9+&@#/%=.~*'()|\[\]]""".toRegex()
private val userInfoPattern = "^(.+?):(.*)$".toRegex()
private val legacyPattern = "^(.+?):(.*)@(.+?):(\\d+?)$".toRegex()
......
......@@ -163,7 +163,7 @@ object PluginManager {
var initialized = false
fun entryNotFound(): Nothing = throw IndexOutOfBoundsException("Plugin entry binary not found")
val list = ArrayList<String>()
val pluginDir = File(Core.deviceStorage.filesDir, "plugin")
val pluginDir = File(Core.deviceStorage.noBackupFilesDir, "plugin")
(cr.query(uri, arrayOf(PluginContract.COLUMN_PATH, PluginContract.COLUMN_MODE), null, null, null)
?: return null).use { cursor ->
if (!cursor.moveToFirst()) entryNotFound()
......
......@@ -21,7 +21,6 @@
package com.github.shadowsocks.preference
import android.os.Binder
import androidx.appcompat.app.AppCompatDelegate
import androidx.preference.PreferenceDataStore
import com.github.shadowsocks.Core
import com.github.shadowsocks.database.PrivateDatabase
......
#define LOG_TAG "JniHelper"
#include "jni.h"
#include <android/log.h>
#include <sys/system_properties.h>
#include <algorithm>
#include <cerrno>
#include <csignal>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/un.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <ancillary.h>
using namespace std;
#define LOGI(...) do { __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__); } while(0)
#define LOGW(...) do { __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__); } while(0)
#define LOGE(...) do { __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__); } while(0)
#define THROW(env, clazz, msg) do { env->ThrowNew(env->FindClass(clazz), msg); } while (0)
static int sdk_version;
static jclass ProcessImpl;
static jfieldID ProcessImpl_pid, ProcessImpl_exitValue, ProcessImpl_exitValueMutex;
#pragma clang diagnostic ignored "-Wunused-parameter"
extern "C" {
JNIEXPORT jint JNICALL Java_com_github_shadowsocks_JniHelper_sigkill(JNIEnv *env, jobject thiz, jint pid) {
// Suppress "No such process" errors. We just want the process killed. It's fine if it's already killed.
return kill(pid, SIGKILL) == -1 && errno != ESRCH ? errno : 0;
}
JNIEXPORT jint JNICALL Java_com_github_shadowsocks_JniHelper_sigterm(JNIEnv *env, jobject thiz, jobject process) {
if (!env->IsInstanceOf(process, ProcessImpl)) {
THROW(env, "java/lang/ClassCastException",
"Unsupported process object. Only java.lang.ProcessManager$ProcessImpl is accepted.");
return -1;
// Based on: https://android.googlesource.com/platform/libcore/+/564c7e8/luni/src/main/native/libcore_io_Linux.cpp#256
static void throwException(JNIEnv* env, jclass exceptionClass, jmethodID ctor2, const char* functionName, int error) {
jstring detailMessage = env->NewStringUTF(functionName);
if (detailMessage == NULL) {
// Not really much we can do here. We're probably dead in the water,
// but let's try to stumble on...
env->ExceptionClear();
}
jint pid = env->GetIntField(process, ProcessImpl_pid);
// Suppress "No such process" errors. We just want the process killed. It's fine if it's already killed.
return kill(pid, SIGTERM) == -1 && errno != ESRCH ? errno : 0;
env->Throw(reinterpret_cast<jthrowable>(env->NewObject(exceptionClass, ctor2, detailMessage, error)));
env->DeleteLocalRef(detailMessage);
}
JNIEXPORT jobject JNICALL
Java_com_github_shadowsocks_JniHelper_getExitValue(JNIEnv *env, jobject thiz, jobject process) {
if (!env->IsInstanceOf(process, ProcessImpl)) {
THROW(env, "java/lang/ClassCastException",
"Unsupported process object. Only java.lang.ProcessManager$ProcessImpl is accepted.");
return NULL;
}
return env->GetObjectField(process, ProcessImpl_exitValue);
}
JNIEXPORT jobject JNICALL
Java_com_github_shadowsocks_JniHelper_getExitValueMutex(JNIEnv *env, jobject thiz, jobject process) {
if (!env->IsInstanceOf(process, ProcessImpl)) {
THROW(env, "java/lang/ClassCastException",
"Unsupported process object. Only java.lang.ProcessManager$ProcessImpl is accepted.");
return NULL;
}
return env->GetObjectField(process, ProcessImpl_exitValueMutex);
}
JNIEXPORT void JNICALL Java_com_github_shadowsocks_JniHelper_close(JNIEnv *env, jobject thiz, jint fd) {
close(fd);
static void throwErrnoException(JNIEnv* env, const char* functionName) {
int error = errno;
static jclass ErrnoException = env->FindClass("android/system/ErrnoException");
static jmethodID ctor2 = env->GetMethodID(ErrnoException, "<init>", "(Ljava/lang/String;I)V");
throwException(env, ErrnoException, ctor2, functionName, error);
}
JNIEXPORT jint JNICALL
#pragma clang diagnostic ignored "-Wunused-parameter"
extern "C" {
JNIEXPORT void JNICALL
Java_com_github_shadowsocks_JniHelper_sendFd(JNIEnv *env, jobject thiz, jint tun_fd, jstring path) {
int fd;
struct sockaddr_un addr;
const char *sock_str = env->GetStringUTFChars(path, 0);
const char *sock_str = env->GetStringUTFChars(path, 0);
if ( (fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
LOGE("socket() failed: %s (socket fd = %d)\n", strerror(errno), fd);
return (jint)-1;
if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
throwErrnoException(env, "socket");
goto quit2;
}
memset(&addr, 0, sizeof(addr));
......@@ -88,20 +47,17 @@ JNIEXPORT jint JNICALL
strncpy(addr.sun_path, sock_str, sizeof(addr.sun_path)-1);
if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
LOGE("connect() failed: %s (fd = %d)\n", strerror(errno), fd);
close(fd);
return (jint)-1;
throwErrnoException(env, "connect");
goto quit;
}
if (ancil_send_fd(fd, tun_fd)) {
LOGE("ancil_send_fd: %s", strerror(errno));
close(fd);
return (jint)-1;
}
if (ancil_send_fd(fd, tun_fd)) throwErrnoException(env, "ancil_send_fd");
quit:
close(fd);
quit2:
env->ReleaseStringUTFChars(path, sock_str);
return 0;
return;
}
JNIEXPORT jbyteArray JNICALL
......@@ -124,50 +80,7 @@ Java_com_github_shadowsocks_JniHelper_parseNumericAddress(JNIEnv *env, jobject t
/*
* This is called by the VM when the shared library is first loaded.
*/
typedef union {
JNIEnv* env;
void* venv;
} UnionJNIEnvToVoid;
#pragma ide diagnostic ignored "OCUnusedGlobalDeclarationInspection"
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
UnionJNIEnvToVoid uenv;
uenv.venv = NULL;
jint result = -1;
JNIEnv* env = NULL;
if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_6) != JNI_OK) {
THROW(env, "java/lang/RuntimeException", "GetEnv failed");
goto bail;
}
env = uenv.env;
char version[PROP_VALUE_MAX + 1];
__system_property_get("ro.build.version.sdk", version);
sdk_version = atoi(version);
#define FIND_CLASS(out, name) \
if (!(out = env->FindClass(name))) { \
THROW(env, "java/lang/RuntimeException", name " not found"); \
goto bail; \
} \
out = reinterpret_cast<jclass>(env->NewGlobalRef(reinterpret_cast<jobject>(out)))
#define GET_FIELD(out, clazz, name, sig) \
if (!(out = env->GetFieldID(clazz, name, sig))) { \
THROW(env, "java/lang/RuntimeException", "Field " #clazz "." name " with type " sig " not found"); \
goto bail; \
}
if (sdk_version < 24) {
FIND_CLASS(ProcessImpl, "java/lang/ProcessManager$ProcessImpl");
GET_FIELD(ProcessImpl_pid, ProcessImpl, "pid", "I")
GET_FIELD(ProcessImpl_exitValue, ProcessImpl, "exitValue", "Ljava/lang/Integer;")
GET_FIELD(ProcessImpl_exitValueMutex, ProcessImpl, "exitValueMutex", "Ljava/lang/Object;")
}
result = JNI_VERSION_1_6;
bail:
return result;
return JNI_VERSION_1_6;
}
Subproject commit a9b5a94e215c1beadfe11442994b550e1e81f8d6
Subproject commit ffc00f97fa7663a91011a10e46ae954cfda3837f
# https://github.com/arturbosch/detekt/blob/RC8/detekt-cli/src/main/resources/default-detekt-config.yml
# https://github.com/arturbosch/detekt/blob/RC9.2/detekt-cli/src/main/resources/default-detekt-config.yml
autoCorrect: true
comments:
......@@ -120,6 +120,7 @@ exceptions:
- IndexOutOfBoundsException
- RuntimeException
- Throwable
allowedExceptionNameRegex: "^(_|(ignore|expected).*)"
TooGenericExceptionThrown:
active: true
exceptionNames:
......@@ -143,8 +144,7 @@ formatting:
active: true
autoCorrect: true
ImportOrdering:
active: true
autoCorrect: false
active: false
Indentation:
active: true
autoCorrect: true
......@@ -217,6 +217,11 @@ naming:
ClassNaming:
active: true
classPattern: '[A-Z$][a-zA-Z0-9$]*'
ConstructorParameterNaming:
active: true
parameterPattern: '[a-z][A-Za-z0-9]*'
privateParameterPattern: '[a-z][A-Za-z0-9]*'
excludeClassPattern: '$^'
EnumNaming:
active: true
enumEntryPattern: '^[A-Z][_a-zA-Z0-9]*'
......@@ -233,14 +238,20 @@ naming:
active: true
functionPattern: '^([a-z$][a-zA-Z$0-9]*)|(`.*`)$'
excludeClassPattern: '$^'
ignoreOverridden: true
FunctionParameterNaming:
active: true
parameterPattern: '[a-z][A-Za-z0-9]*'
excludeClassPattern: '$^'
MatchingDeclarationName:
active: true
MemberNameEqualsClassName:
active: false
ObjectPropertyNaming:
active: true
propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
constantPattern: '[A-Za-z][_A-Za-z0-9]*'
propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
privatePropertyPattern: '(_)?[A-Za-z][A-Za-z0-9]*'
PackageNaming:
active: true
packagePattern: '^[a-z]+(\.[a-z][a-z0-9]*)*$'
......@@ -259,6 +270,7 @@ naming:
variablePattern: '[a-z][A-Za-z0-9]*'
privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*'
excludeClassPattern: '$^'
ignoreOverridden: true
performance:
active: true
......@@ -318,6 +330,8 @@ style:
ForbiddenImport:
active: true
imports: ''
ForbiddenVoid:
active: true
FunctionOnlyReturningConstant:
active: true
ignoreOverridableFunction: true
......@@ -372,8 +386,11 @@ style:
active: true
UnnecessaryAbstractClass:
active: true
excludeAnnotatedClasses: "dagger.Module"
UnnecessaryInheritance:
active: true
UnnecessaryLet:
active: true
UnnecessaryParentheses:
active: true
UntilInsteadOfRangeTo:
......
......@@ -11,7 +11,6 @@
# The setting is particularly useful for tweaking memory settings.
android.enableJetifier=true
android.useAndroidX=true
org.gradle.jvmargs=-Xmx2g
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
......
......@@ -55,7 +55,7 @@ android {
dependencies {
implementation project(':core')
implementation "androidx.browser:browser:$androidxVersion"
implementation "androidx.browser:browser:1.0.0"
implementation 'com.google.android.gms:play-services-vision:15.0.2'
implementation 'com.google.firebase:firebase-ads:15.0.1'
implementation "com.takisoft.preferencex:preferencex-simplemenu:$preferencexVersion"
......
......@@ -24,7 +24,6 @@ import android.app.Application
import android.content.res.Configuration
import androidx.appcompat.app.AppCompatDelegate
import com.github.shadowsocks.preference.BottomSheetPreferenceDialogFragment
import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.preference.IconListPreference
import com.takisoft.preferencex.PreferenceFragmentCompat
......
......@@ -62,7 +62,7 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener,
private const val SELECTED_URLS = "com.github.shadowsocks.acl.CustomRulesFragment.SELECTED_URLS"
// unescaped: (?<=^(\(\^\|\\\.\)|\^\(\.\*\\\.\)\?)).*(?=\$$)
private val PATTERN_DOMAIN = "(?<=^(\\(\\^\\|\\\\\\.\\)|\\^\\(\\.\\*\\\\\\.\\)\\?)).*(?=\\\$\$)".toRegex()
private val domainPattern = "(?<=^(\\(\\^\\|\\\\\\.\\)|\\^\\(\\.\\*\\\\\\.\\)\\?)).*(?=\\\$\$)".toRegex()
}
private enum class Template {
......@@ -86,7 +86,7 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener,
inputLayout = view.findViewById(R.id.content_layout)
when (item) {
is String -> {
val match = PATTERN_DOMAIN.find(item)
val match = domainPattern.find(item)
if (match != null) {
templateSelector.setSelection(Template.Domain.ordinal)
editText.setText(IDN.toUnicode(match.value.replace("\\.", "."),
......
......@@ -4,20 +4,17 @@
<item android:id="@+id/action_apply_all"
android:title="@string/action_apply_all"
android:alphabeticShortcut="a"
android:numericShortcut="1"
app:showAsAction="never"/>
<item
android:id="@+id/action_export_clipboard"
android:alphabeticShortcut="c"
android:icon="?attr/actionModeCopyDrawable"
android:numericShortcut="2"
android:title="@string/action_export"
app:showAsAction="ifRoom"/>
<item
android:id="@+id/action_import_clipboard"
android:alphabeticShortcut="v"
android:icon="?attr/actionModePasteDrawable"
android:numericShortcut="3"
android:title="@string/action_import"
app:showAsAction="ifRoom"/>
</menu>
......@@ -4,7 +4,6 @@
<item android:title="@string/action_add_rule"
android:icon="@drawable/ic_av_playlist_add"
android:alphabeticShortcut="n"
android:numericShortcut="1"
app:showAsAction="always">
<menu>
<item android:id="@+id/action_manual_settings"
......
......@@ -6,28 +6,24 @@
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="x"
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>
......@@ -5,12 +5,10 @@
android:title="@string/delete"
android:icon="@drawable/ic_action_delete"
android:alphabeticShortcut="d"
android:numericShortcut="1"
app:showAsAction="always"/>
<item android:id="@+id/action_apply"
android:title="@string/apply"
android:icon="@drawable/ic_action_done"
android:alphabeticShortcut="s"
android:numericShortcut="2"
app:showAsAction="always"/>
</menu>
......@@ -4,7 +4,6 @@
<item
android:alphabeticShortcut="n"
android:icon="@drawable/ic_action_note_add"
android:numericShortcut="1"
android:title="@string/add_profile"
app:showAsAction="always">
<menu>
......@@ -28,7 +27,6 @@
android:alphabeticShortcut="s"
android:icon="@drawable/ic_file_file_upload"
android:title="@string/action_export_more"
android:numericShortcut="2"
app:showAsAction="always">
<menu>
<item
......
......@@ -5,6 +5,5 @@
android:title="@string/action_import_file"
android:icon="@drawable/ic_image_photo"
android:alphabeticShortcut="o"
android:numericShortcut="1"
app:showAsAction="always"/>
</menu>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="StyledScrollerTextAppearance" parent="@android:style/TextAppearance">
<item name="android:textSize">24sp</item>
<item name="android:textStyle">bold</item>
<item name="android:textColor">@android:color/white</item>
</style>
</resources>
## Privacy Policy
Shadowsocks built the Shadowsocks-android app as an Open Source app. This SERVICE is provided by Shadowsocks at no cost and is intended for use as is.
This page is used to inform visitors regarding my policies with the collection, use, and disclosure of Personal Information if anyone decided to use my Service.
If you choose to use my Service, then you agree to the collection and use of information in relation to this policy. The Personal Information that I collect is used for providing and improving the Service. I will not use or share your information with anyone except as described in this Privacy Policy.
The terms used in this Privacy Policy have the same meanings as in our Terms and Conditions, which is accessible at Shadowsocks-android unless otherwise defined in this Privacy Policy.
**Information Collection and Use**
For a better experience, while using our Service, I may require you to provide us with certain personally identifiable information. The information that I request will be retained on your device and is not collected by me in any way.
The app does use third party services that may collect information used to identify you.
Link to privacy policy of third party service providers used by the app
* [Google Play Services](https://www.google.com/policies/privacy/)
**Log Data**
I want to inform you that whenever you use my Service, in a case of an error in the app I collect data and information (through third party products) on your phone called Log Data. This Log Data may include information such as your device Internet Protocol (“IP”) address, device name, operating system version, the configuration of the app when utilizing my Service, the time and date of your use of the Service, and other statistics.
**Cookies**
Cookies are files with a small amount of data that are commonly used as anonymous unique identifiers. These are sent to your browser from the websites that you visit and are stored on your device's internal memory.
This Service does not use these “cookies” explicitly. However, the app may use third party code and libraries that use “cookies” to collect information and improve their services. You have the option to either accept or refuse these cookies and know when a cookie is being sent to your device. If you choose to refuse our cookies, you may not be able to use some portions of this Service.
**Service Providers**
I may employ third-party companies and individuals due to the following reasons:
* To facilitate our Service;
* To provide the Service on our behalf;
* To perform Service-related services; or
* To assist us in analyzing how our Service is used.
I want to inform users of this Service that these third parties have access to your Personal Information. The reason is to perform the tasks assigned to them on our behalf. However, they are obligated not to disclose or use the information for any other purpose.
**Security**
I value your trust in providing us your Personal Information, thus we are striving to use commercially acceptable means of protecting it. But remember that no method of transmission over the internet, or method of electronic storage is 100% secure and reliable, and I cannot guarantee its absolute security.
**Links to Other Sites**
This Service may contain links to other sites. If you click on a third-party link, you will be directed to that site. Note that these external sites are not operated by me. Therefore, I strongly advise you to review the Privacy Policy of these websites. I have no control over and assume no responsibility for the content, privacy policies, or practices of any third-party sites or services.
**Children’s Privacy**
These Services do not address anyone under the age of 13\. I do not knowingly collect personally identifiable information from children under 13\. In the case I discover that a child under 13 has provided me with personal information, I immediately delete this from our servers. If you are a parent or guardian and you are aware that your child has provided us with personal information, please contact me so that I will be able to do necessary actions.
**Changes to This Privacy Policy**
I may update our Privacy Policy from time to time. Thus, you are advised to review this page periodically for any changes. I will notify you of any changes by posting the new Privacy Policy on this page. These changes are effective immediately after they are posted on this page.
**Contact Us**
If you have any questions or suggestions about my Privacy Policy, do not hesitate to contact me.
This privacy policy page was created at [privacypolicytemplate.net](https://privacypolicytemplate.net) and modified/generated by [App Privacy Policy Generator](https://app-privacy-policy-generator.firebaseapp.com/)
\ No newline at end of file
......@@ -56,7 +56,7 @@ android {
dependencies {
implementation project(':core')
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "androidx.leanback:leanback-preference:$androidxVersion"
implementation "androidx.leanback:leanback-preference:1.0.0"
}
apply plugin: 'com.google.gms.google-services'
......
......@@ -294,6 +294,7 @@ class MainPreferenceFragment : LeanbackPreferenceFragment(), ShadowsocksConnecti
}
} catch (e: Exception) {
printLog(e)
Toast.makeText(activity, e.localizedMessage, Toast.LENGTH_SHORT).show()
}
populateProfiles()
}
......
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