Commit 0c335293 authored by Mygod's avatar Mygod

Merge branch 'master' into split-abi

parents cd1f94f4 0d02a39b
...@@ -3,9 +3,8 @@ ...@@ -3,9 +3,8 @@
Cannot connect to server: Cannot connect to server:
1. Stop battery saver if it's active; 1. Stop battery saver if it's active;
2. If an upgrade breaks anything, do a manual reset first by pressing Reset at the end of the list; 2. Check your config;
3. Check your config; 3. Wipe app data.
4. Wipe app data.
Crash: [Submit an issue](https://github.com/shadowsocks/shadowsocks-android/issues/new) with logcat attached, or submit a crash report to Google Play. Then, try wiping app data. Crash: [Submit an issue](https://github.com/shadowsocks/shadowsocks-android/issues/new) with logcat attached, or submit a crash report to Google Play. Then, try wiping app data.
......
### Решение проблем
Не получается подключиться к серверу:
1. Выключите режим энергосбережения, если он активен;
2. Проверьте настройки;
3. Сотрите данные приложения.
Не работает: [Добавьте отчет](https://github.com/shadowsocks/shadowsocks-android/issues/new) прикрепив лог (logcat), или отправьте отзыв в Google Play. Затем попробуйте стереть данные приложения.
### Как добавить виджет и/или переключать профиль на основании состояния сети?
Использйте интеграцию с [Tasker](http://tasker.dinglisch.net/).
### Как добавить QR код из локальной галереи?
Отсканируйте его при помощи стороннего сканера, например [QuickMark Barcode Scanner](https://play.google.com/store/apps/details?id=tw.com.quickmark) и нажмите на `ss` ссылку.
### Почему режим NAT более не поддерживается?
1. Требуются ROOT права;
2. Нет поддержки IPv6;
3. Нет поддержки UDP relay.
### Как убрать восклицательный знак когда используется режим VPN?
Восклицательный знак на значке Wi-Fi/Сотовой связи появляется, потому что система не может подключиться к portal-серверу (по-умолчанию к `clients3.google.com`) без VPN подключения. Чтобы убрать его, следуйте инструкциям в [этом топике](https://www.noisyfox.cn/45.html). (на упрощенном китайском)
### Почему мой ROM не поддерживается?
1. Некоторые ROM используют неправильную реализацию службы VPN, особенно IPv6;
2. Некоторые ROM используют агрессивную (или так называемую неправильную) фоновую службу с ошибочной политикой остановки;
3. Если вы используете фреймворк Xposed и/или приложения для энергосбережения, вероятно наше приложение не будет нормально работать с ними.
* Иправления для MIUI: [#772](https://github.com/shadowsocks/shadowsocks-android/issues/772)
* Иправления для Huawei: [#1091 (комментарий)](https://github.com/shadowsocks/shadowsocks-android/issues/1091#issuecomment-276949836)
* Вызванные из-за Xposed: [#1414](https://github.com/shadowsocks/shadowsocks-android/issues/1414)
* Samsung и/или Brevent: [#1410](https://github.com/shadowsocks/shadowsocks-android/issues/1410)
* Не устанавливайте это приложение на SD карту, из-за проблем с разрешениями: [#1124 (комментарий)](https://github.com/shadowsocks/shadowsocks-android/issues/1124#issuecomment-307556453)
### Как приостановить службу Shadowsocks?
* Для Android 7.0+: Используйте переключатель в быстрых настройках;
* Используйте интеграцию с Tasker;
* Добавьте профиль c выбранными приложениями, которые будут работать через Shadowsocks прокси, выключив обходной режим.
### Почему Shadowsocks потребляет так много энергии на Android 5.0+?
Так как Shadowsocks работает со всем траффиком устройства, заряд батареи используемый другими приложениями также считается за тот, что использует Shadowsocks. Таким образом использование батареи Shadowsocks'ом равен сумме всей активности устройства в сети. Shadowsocks сам по себе в общем-то является приложением связанного процесса ввода/вывода на современных Android устройствах, которое, ожидаемо, не потребляет значительного количества энергии.
Так что если вы заметите значительное увеличение потребляемой энергии после того, как вы использовали Shadowsocks, это скорее всего вызванно другими приложениями. Например, сервисы Google Play могут потреблять больше энергии после того, как подключатся к Google, и т.д.
Больше информации: https://kb.adguard.com/en/android/solving-problems/battery
### Оно прекрасно работает при Wi-Fi но не может подключиться через сотовую связь?
Разрешите этому приложению передачу данных в фоновом режиме в настройках приложения.
Если у вас оперератор Yota, то измените точку доступа APN с `yota.ru` на `client.yota.ru`
### Почему требуется разрешение камере на устройствах младше Android 6.0?
Чтобы сканировать QR код через встроенный QR сканер.
Кстати, обновите уже свою версию Android.
### Как использовать режим Transproxy (прозрачный прокси)?
1. Установите [AFWall+](https://github.com/ukanth/afwall);
2. Настройте пользовательский скрипт:
```sh
IP6TABLES=/system/bin/ip6tables
IPTABLES=/system/bin/iptables
ULIMIT=/system/bin/ulimit
SHADOWSOCKS_UID=`dumpsys package com.github.shadowsocks | grep userId | cut -d= -f2 - | cut -d' ' -f1 -`
PORT_DNS=5450
PORT_TRANSPROXY=8200
$ULIMIT -n 4096
$IP6TABLES -F
$IP6TABLES -A INPUT -j DROP
$IP6TABLES -A OUTPUT -j DROP
$IPTABLES -t nat -F OUTPUT
$IPTABLES -t nat -A OUTPUT -o lo -j RETURN
$IPTABLES -t nat -A OUTPUT -d 127.0.0.1 -j RETURN
$IPTABLES -t nat -A OUTPUT -m owner --uid-owner $SHADOWSOCKS_UID -j RETURN
$IPTABLES -t nat -A OUTPUT -p tcp --dport 53 -j DNAT --to-destination 127.0.0.1:$PORT_DNS
$IPTABLES -t nat -A OUTPUT -p udp --dport 53 -j DNAT --to-destination 127.0.0.1:$PORT_DNS
$IPTABLES -t nat -A OUTPUT -p tcp -j DNAT --to-destination 127.0.0.1:$PORT_TRANSPROXY
$IPTABLES -t nat -A OUTPUT -p udp -j DNAT --to-destination 127.0.0.1:$PORT_TRANSPROXY
```
3. Настройте пользовательский скрипт отключения:
```sh
IP6TABLES=/system/bin/ip6tables
IPTABLES=/system/bin/iptables
$IPTABLES -t nat -F OUTPUT
$IP6TABLES -F
```
4. Убедитесь, что траффик для Shadowsocks разрешен;
5. Запустите службу прозрачного прокси (transproxy) в Shadowsocks и включите брандмауэр.
...@@ -5,34 +5,29 @@ jdk: ...@@ -5,34 +5,29 @@ jdk:
env: env:
global: global:
- NDK_VERSION=r16
- NDK_CCACHE=ccache
- GOROOT_BOOTSTRAP=$GOROOT - GOROOT_BOOTSTRAP=$GOROOT
- ANDROID_NDK_HOME=$HOME/.android/android-ndk-${NDK_VERSION}
- PATH=${ANDROID_NDK_HOME}:${ANDROID_HOME}/tools:${ANDROID_HOME}/platform-tools:${PATH} before_cache:
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
- rm -fr $HOME/.gradle/caches/*/plugin-resolution/
cache: cache:
directories: directories:
- $HOME/.gradle - $HOME/.gradle/caches/
- $HOME/.gradle/wrapper/
android:
components:
- tools
- android-27
- build-tools-26.0.2
- build-tools-27.0.0
install: install:
- > - pushd $ANDROID_HOME
if [ ! -d "$ANDROID_NDK_HOME" ]; then - rm -rf tools
mkdir -p $ANDROID_NDK_HOME; - wget -q https://dl.google.com/android/repository/sdk-tools-linux-4333796.zip -O tools.zip
pushd $HOME/.android; - unzip -q tools.zip
export ARCH=`uname -m`; - popd
wget -q http://dl.google.com/android/repository/android-ndk-${NDK_VERSION}-linux-${ARCH}.zip; - touch $HOME/.android/repositories.cfg
unzip -q android-ndk-${NDK_VERSION}-linux-${ARCH}.zip; - yes | sdkmanager
popd; "build-tools;27.0.3"
fi ndk-bundle
- > "platforms;android-27"
>/dev/null
script: script:
- ./gradlew assembleDebug - ./gradlew assembleDebug check
## Shadowsocks for Android ## Shadowsocks for Android
[![Build Status](https://api.travis-ci.org/shadowsocks/shadowsocks-android.svg)](https://travis-ci.org/shadowsocks/shadowsocks-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) [![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 [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> <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>
......
...@@ -2,12 +2,13 @@ ...@@ -2,12 +2,13 @@
buildscript { buildscript {
ext { ext {
kotlinVersion = '1.2.0' kotlinVersion = '1.2.20'
minSdkVersion = 19 minSdkVersion = 21
sdkVersion = 27 sdkVersion = 27
buildToolsVersion = '27.0.3'
supportLibraryVersion = '27.0.2' supportLibraryVersion = '27.0.2'
takisoftFixVersion = '27.0.2.0' takisoftFixVersion = '27.0.2.0'
playServicesVersion = '11.6.2' playServicesVersion = '11.8.0'
junitVersion = '4.12' junitVersion = '4.12'
androidTestVersion = '1.0.1' androidTestVersion = '1.0.1'
androidEspressoVersion = '3.0.1' androidEspressoVersion = '3.0.1'
......
import org.apache.tools.ant.taskdefs.condition.Os
apply plugin: 'com.android.library' apply plugin: 'com.android.library'
android { android {
buildToolsVersion rootProject.buildToolsVersion
compileSdkVersion rootProject.sdkVersion compileSdkVersion rootProject.sdkVersion
defaultConfig { defaultConfig {
...@@ -33,18 +36,28 @@ android { ...@@ -33,18 +36,28 @@ android {
} }
task goBuild(type: Exec) { task goBuild(type: Exec) {
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
executable "cmd.exe"
args "/c", "src/overture/make.bat " + minSdkVersion
} else {
executable "sh" executable "sh"
args "-c", "src/overture/make.bash " + minSdkVersion args "-c", "src/overture/make.bash " + minSdkVersion
}
} }
task goClean(type: Exec) { task goClean(type: Exec) {
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
executable "cmd.exe"
args "/c", "src/overture/clean.bat"
} else {
executable "sh" executable "sh"
args "-c", "src/overture/clean.bash" args "-c", "src/overture/clean.bash"
}
} }
tasks.whenTaskAdded { task -> tasks.whenTaskAdded { task ->
if (task.name == 'generateJsonModelDebug' || if ((task.name == 'generateJsonModelDebug' ||
task.name == 'generateJsonModelRelease') { task.name == 'generateJsonModelRelease')) {
task.dependsOn(goBuild) task.dependsOn(goBuild)
} }
} }
......
...@@ -21,6 +21,6 @@ public class ExampleInstrumentedTest { ...@@ -21,6 +21,6 @@ public class ExampleInstrumentedTest {
// Context of the app under test. // Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext(); Context appContext = InstrumentationRegistry.getTargetContext();
assertEquals("com.github.shadowsockscorelibraries.test", appContext.getPackageName()); assertEquals("com.github.shadowsocks.core.test", appContext.getPackageName());
} }
} }
...@@ -39,6 +39,8 @@ ...@@ -39,6 +39,8 @@
package com.github.shadowsocks; package com.github.shadowsocks;
import android.os.Build; import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.system.ErrnoException; import android.system.ErrnoException;
public class JniHelper { public class JniHelper {
...@@ -47,15 +49,14 @@ public class JniHelper { ...@@ -47,15 +49,14 @@ public class JniHelper {
} }
@Deprecated // Use Process.destroy() since API 24 @Deprecated // Use Process.destroy() since API 24
public static void sigtermCompat(Process process) throws Exception { public static void sigtermCompat(@NonNull Process process) throws Exception {
if (Build.VERSION.SDK_INT >= 24) throw new UnsupportedOperationException("Never call this method in OpenJDK!"); if (Build.VERSION.SDK_INT >= 24) throw new UnsupportedOperationException("Never call this method in OpenJDK!");
int errno = sigterm(process); int errno = sigterm(process);
if (errno != 0) throw Build.VERSION.SDK_INT >= 21 if (errno != 0) throw new ErrnoException("kill", errno);
? new ErrnoException("kill", errno) : new Exception("kill failed: " + errno);
} }
@Deprecated // only implemented for before API 24 @Deprecated // only implemented for before API 24
public static boolean waitForCompat(Process process, long millis) throws Exception { 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!"); if (Build.VERSION.SDK_INT >= 24) throw new UnsupportedOperationException("Never call this method in OpenJDK!");
final Object mutex = getExitValueMutex(process); final Object mutex = getExitValueMutex(process);
synchronized (mutex) { synchronized (mutex) {
...@@ -68,6 +69,8 @@ public class JniHelper { ...@@ -68,6 +69,8 @@ public class JniHelper {
private static native int sigterm(Process process); private static native int sigterm(Process process);
private static native Integer getExitValue(Process process); private static native Integer getExitValue(Process process);
private static native Object getExitValueMutex(Process process); private static native Object getExitValueMutex(Process process);
public static native int sendFd(int fd, String path); public static native int sendFd(int fd, @NonNull String path);
public static native void close(int fd); public static native void close(int fd);
@Nullable
public static native byte[] parseNumericAddress(@NonNull String str);
} }
...@@ -62,7 +62,7 @@ LOCAL_CFLAGS += -I$(LOCAL_PATH)/libsodium/src/libsodium/include \ ...@@ -62,7 +62,7 @@ LOCAL_CFLAGS += -I$(LOCAL_PATH)/libsodium/src/libsodium/include \
-I$(LOCAL_PATH)/include/sodium \ -I$(LOCAL_PATH)/include/sodium \
-I$(LOCAL_PATH)/libsodium/src/libsodium/include/sodium \ -I$(LOCAL_PATH)/libsodium/src/libsodium/include/sodium \
-DPACKAGE_NAME=\"libsodium\" -DPACKAGE_TARNAME=\"libsodium\" \ -DPACKAGE_NAME=\"libsodium\" -DPACKAGE_TARNAME=\"libsodium\" \
-DPACKAGE_VERSION=\"1.0.15\" -DPACKAGE_STRING=\"libsodium\ 1.0.15\" \ -DPACKAGE_VERSION=\"1.0.15\" -DPACKAGE_STRING=\"libsodium-1.0.15\" \
-DPACKAGE_BUGREPORT=\"https://github.com/jedisct1/libsodium/issues\" \ -DPACKAGE_BUGREPORT=\"https://github.com/jedisct1/libsodium/issues\" \
-DPACKAGE_URL=\"https://github.com/jedisct1/libsodium\" \ -DPACKAGE_URL=\"https://github.com/jedisct1/libsodium\" \
-DPACKAGE=\"libsodium\" -DVERSION=\"1.0.15\" \ -DPACKAGE=\"libsodium\" -DVERSION=\"1.0.15\" \
...@@ -318,6 +318,8 @@ include $(CLEAR_VARS) ...@@ -318,6 +318,8 @@ include $(CLEAR_VARS)
LOCAL_MODULE:= jni-helper LOCAL_MODULE:= jni-helper
LOCAL_CFLAGS := -std=c++11
LOCAL_C_INCLUDES:= $(LOCAL_PATH)/libancillary LOCAL_C_INCLUDES:= $(LOCAL_PATH)/libancillary
LOCAL_SRC_FILES:= jni-helper.cpp LOCAL_SRC_FILES:= jni-helper.cpp
...@@ -450,7 +452,6 @@ LOCAL_CFLAGS += -DHAVE_CONFIG_H ...@@ -450,7 +452,6 @@ LOCAL_CFLAGS += -DHAVE_CONFIG_H
LOCAL_C_INCLUDES := $(LOCAL_PATH)/pcre/dist $(LOCAL_PATH)/pcre LOCAL_C_INCLUDES := $(LOCAL_PATH)/pcre/dist $(LOCAL_PATH)/pcre
libpcre_src_files := \ libpcre_src_files := \
pcre_chartables.c \
dist/pcre_byte_order.c \ dist/pcre_byte_order.c \
dist/pcre_compile.c \ dist/pcre_compile.c \
dist/pcre_config.c \ dist/pcre_config.c \
...@@ -472,7 +473,7 @@ libpcre_src_files := \ ...@@ -472,7 +473,7 @@ libpcre_src_files := \
dist/pcre_version.c \ dist/pcre_version.c \
dist/pcre_xclass.c dist/pcre_xclass.c
LOCAL_SRC_FILES := $(addprefix pcre/, $(libpcre_src_files)) LOCAL_SRC_FILES := $(addprefix pcre/, $(libpcre_src_files)) $(LOCAL_PATH)/patch/pcre/pcre_chartables.c
include $(BUILD_STATIC_LIBRARY) include $(BUILD_STATIC_LIBRARY)
......
APP_ABI := armeabi-v7a arm64-v8a x86
APP_PLATFORM := android-21
APP_STL := c++_static
NDK_TOOLCHAIN_VERSION := clang
...@@ -16,8 +16,8 @@ ...@@ -16,8 +16,8 @@
# executable program # executable program
# #
# Modified by @Mygod, based on: # Modified by @Mygod, based on:
# https://android.googlesource.com/platform/ndk/+/f2e98f8c066aed59caf61163d4b87c2b858f9814/build/core/build-shared-library.mk # https://android.googlesource.com/platform/ndk/+/a355a4e/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-executable.mk
LOCAL_BUILD_SCRIPT := BUILD_EXECUTABLE LOCAL_BUILD_SCRIPT := BUILD_EXECUTABLE
LOCAL_MAKEFILE := $(local-makefile) LOCAL_MAKEFILE := $(local-makefile)
$(call check-defined-LOCAL_MODULE,$(LOCAL_BUILD_SCRIPT)) $(call check-defined-LOCAL_MODULE,$(LOCAL_BUILD_SCRIPT))
......
...@@ -4,33 +4,33 @@ ...@@ -4,33 +4,33 @@
#include <android/log.h> #include <android/log.h>
#include <sys/system_properties.h> #include <sys/system_properties.h>
#include <stdio.h> #include <algorithm>
#include <stdlib.h> #include <cerrno>
#include <string.h> #include <csignal>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <arpa/inet.h>
#include <unistd.h> #include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <sys/un.h> #include <sys/un.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <ancillary.h> #include <ancillary.h>
using namespace std;
#define LOGI(...) do { __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__); } while(0) #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 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 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) #define THROW(env, clazz, msg) do { env->ThrowNew(env->FindClass(clazz), msg); } while (0)
static int sdk_version;
static jclass ProcessImpl; static jclass ProcessImpl;
static jfieldID ProcessImpl_pid, ProcessImpl_exitValue, ProcessImpl_exitValueMutex; static jfieldID ProcessImpl_pid, ProcessImpl_exitValue, ProcessImpl_exitValueMutex;
static int sdk_version() { #pragma clang diagnostic ignored "-Wunused-parameter"
char version[PROP_VALUE_MAX + 1];
__system_property_get("ro.build.version.sdk", version);
return atoi(version);
}
extern "C" { extern "C" {
JNIEXPORT jint JNICALL Java_com_github_shadowsocks_JniHelper_sigkill(JNIEnv *env, jobject thiz, jint pid) { 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. // Suppress "No such process" errors. We just want the process killed. It's fine if it's already killed.
...@@ -103,6 +103,22 @@ JNIEXPORT jint JNICALL ...@@ -103,6 +103,22 @@ JNIEXPORT jint JNICALL
env->ReleaseStringUTFChars(path, sock_str); env->ReleaseStringUTFChars(path, sock_str);
return 0; return 0;
} }
JNIEXPORT jbyteArray JNICALL
Java_com_github_shadowsocks_JniHelper_parseNumericAddress(JNIEnv *env, jobject thiz, jstring str) {
const char *src = env->GetStringUTFChars(str, 0);
jbyte dst[max(sizeof(in_addr), sizeof(in6_addr))];
jbyteArray arr = nullptr;
if (inet_pton(AF_INET, src, dst) == 1) {
arr = env->NewByteArray(sizeof(in_addr));
env->SetByteArrayRegion(arr, 0, sizeof(in_addr), dst);
} else if (inet_pton(AF_INET6, src, dst) == 1) {
arr = env->NewByteArray(sizeof(in6_addr));
env->SetByteArrayRegion(arr, 0, sizeof(in6_addr), dst);
}
env->ReleaseStringUTFChars(str, src);
return arr;
}
} }
/* /*
...@@ -114,6 +130,7 @@ typedef union { ...@@ -114,6 +130,7 @@ typedef union {
void* venv; void* venv;
} UnionJNIEnvToVoid; } UnionJNIEnvToVoid;
#pragma ide diagnostic ignored "OCUnusedGlobalDeclarationInspection"
jint JNI_OnLoad(JavaVM* vm, void* reserved) { jint JNI_OnLoad(JavaVM* vm, void* reserved) {
UnionJNIEnvToVoid uenv; UnionJNIEnvToVoid uenv;
uenv.venv = NULL; uenv.venv = NULL;
...@@ -126,24 +143,27 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) { ...@@ -126,24 +143,27 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
} }
env = uenv.env; env = uenv.env;
if (sdk_version() < 24) { char version[PROP_VALUE_MAX + 1];
if (!(ProcessImpl = env->FindClass("java/lang/ProcessManager$ProcessImpl"))) { __system_property_get("ro.build.version.sdk", version);
THROW(env, "java/lang/RuntimeException", "ProcessManager$ProcessImpl not found"); sdk_version = atoi(version);
goto bail;
} #define FIND_CLASS(out, name) \
ProcessImpl = (jclass) env->NewGlobalRef((jobject) ProcessImpl); if (!(out = env->FindClass(name))) { \
if (!(ProcessImpl_pid = env->GetFieldID(ProcessImpl, "pid", "I"))) { THROW(env, "java/lang/RuntimeException", name " not found"); \
THROW(env, "java/lang/RuntimeException", "ProcessManager$ProcessImpl.pid not found"); goto bail; \
goto bail; } \
} out = reinterpret_cast<jclass>(env->NewGlobalRef(reinterpret_cast<jobject>(out)))
if (!(ProcessImpl_exitValue = env->GetFieldID(ProcessImpl, "exitValue", "Ljava/lang/Integer;"))) { #define GET_FIELD(out, clazz, name, sig) \
THROW(env, "java/lang/RuntimeException", "ProcessManager$ProcessImpl.exitValue not found"); if (!(out = env->GetFieldID(clazz, name, sig))) { \
goto bail; THROW(env, "java/lang/RuntimeException", "Field " #clazz "." name " with type " sig " not found"); \
} goto bail; \
if (!(ProcessImpl_exitValueMutex = env->GetFieldID(ProcessImpl, "exitValueMutex", "Ljava/lang/Object;"))) {
THROW(env, "java/lang/RuntimeException", "ProcessManager$ProcessImpl.exitValueMutex 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; result = JNI_VERSION_1_6;
......
/*************************************************
* Perl-Compatible Regular Expressions *
*************************************************/
/* This file contains character tables that are used when no external tables
are passed to PCRE by the application that calls it. The tables are used only
for characters whose code values are less than 256.
This is a default version of the tables that assumes ASCII encoding. A program
called dftables (which is distributed with PCRE) can be used to build
alternative versions of this file. This is necessary if you are running in an
EBCDIC environment, or if you want to default to a different encoding, for
example ISO-8859-1. When dftables is run, it creates these tables in the
current locale. If PCRE is configured with --enable-rebuild-chartables, this
happens automatically.
The following #includes are present because without them gcc 4.x may remove the
array definition from the final binary if PCRE is built into a static library
and dead code stripping is activated. This leads to link errors. Pulling in the
header ensures that the array gets flagged as "someone outside this compilation
unit might reference this" and so it will always be supplied to the linker. */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "pcre_internal.h"
const pcre_uint8 PRIV(default_tables)[] = {
/* This table is a lower casing table. */
0, 1, 2, 3, 4, 5, 6, 7,
8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27, 28, 29, 30, 31,
32, 33, 34, 35, 36, 37, 38, 39,
40, 41, 42, 43, 44, 45, 46, 47,
48, 49, 50, 51, 52, 53, 54, 55,
56, 57, 58, 59, 60, 61, 62, 63,
64, 97, 98, 99,100,101,102,103,
104,105,106,107,108,109,110,111,
112,113,114,115,116,117,118,119,
120,121,122, 91, 92, 93, 94, 95,
96, 97, 98, 99,100,101,102,103,
104,105,106,107,108,109,110,111,
112,113,114,115,116,117,118,119,
120,121,122,123,124,125,126,127,
128,129,130,131,132,133,134,135,
136,137,138,139,140,141,142,143,
144,145,146,147,148,149,150,151,
152,153,154,155,156,157,158,159,
160,161,162,163,164,165,166,167,
168,169,170,171,172,173,174,175,
176,177,178,179,180,181,182,183,
184,185,186,187,188,189,190,191,
192,193,194,195,196,197,198,199,
200,201,202,203,204,205,206,207,
208,209,210,211,212,213,214,215,
216,217,218,219,220,221,222,223,
224,225,226,227,228,229,230,231,
232,233,234,235,236,237,238,239,
240,241,242,243,244,245,246,247,
248,249,250,251,252,253,254,255,
/* This table is a case flipping table. */
0, 1, 2, 3, 4, 5, 6, 7,
8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27, 28, 29, 30, 31,
32, 33, 34, 35, 36, 37, 38, 39,
40, 41, 42, 43, 44, 45, 46, 47,
48, 49, 50, 51, 52, 53, 54, 55,
56, 57, 58, 59, 60, 61, 62, 63,
64, 97, 98, 99,100,101,102,103,
104,105,106,107,108,109,110,111,
112,113,114,115,116,117,118,119,
120,121,122, 91, 92, 93, 94, 95,
96, 65, 66, 67, 68, 69, 70, 71,
72, 73, 74, 75, 76, 77, 78, 79,
80, 81, 82, 83, 84, 85, 86, 87,
88, 89, 90,123,124,125,126,127,
128,129,130,131,132,133,134,135,
136,137,138,139,140,141,142,143,
144,145,146,147,148,149,150,151,
152,153,154,155,156,157,158,159,
160,161,162,163,164,165,166,167,
168,169,170,171,172,173,174,175,
176,177,178,179,180,181,182,183,
184,185,186,187,188,189,190,191,
192,193,194,195,196,197,198,199,
200,201,202,203,204,205,206,207,
208,209,210,211,212,213,214,215,
216,217,218,219,220,221,222,223,
224,225,226,227,228,229,230,231,
232,233,234,235,236,237,238,239,
240,241,242,243,244,245,246,247,
248,249,250,251,252,253,254,255,
/* This table contains bit maps for various character classes. Each map is 32
bytes long and the bits run from the least significant end of each byte. The
classes that have their own maps are: space, xdigit, digit, upper, lower, word,
graph, print, punct, and cntrl. Other classes are built from combinations. */
0x00,0x3e,0x00,0x00,0x01,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x03,
0x7e,0x00,0x00,0x00,0x7e,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x03,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0xfe,0xff,0xff,0x07,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0x07,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x03,
0xfe,0xff,0xff,0x87,0xfe,0xff,0xff,0x07,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7f,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0xfe,0xff,0x00,0xfc,
0x01,0x00,0x00,0xf8,0x01,0x00,0x00,0x78,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
/* This table identifies various classes of character by individual bits:
0x01 white space character
0x02 letter
0x04 decimal digit
0x08 hexadecimal digit
0x10 alphanumeric or '_'
0x80 regular expression metacharacter or binary zero
*/
0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 0- 7 */
0x00,0x01,0x01,0x01,0x01,0x01,0x00,0x00, /* 8- 15 */
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 16- 23 */
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 24- 31 */
0x01,0x00,0x00,0x00,0x80,0x00,0x00,0x00, /* - ' */
0x80,0x80,0x80,0x80,0x00,0x00,0x80,0x00, /* ( - / */
0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c, /* 0 - 7 */
0x1c,0x1c,0x00,0x00,0x00,0x00,0x00,0x80, /* 8 - ? */
0x00,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x12, /* @ - G */
0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12, /* H - O */
0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12, /* P - W */
0x12,0x12,0x12,0x80,0x80,0x00,0x80,0x10, /* X - _ */
0x00,0x1a,0x1a,0x1a,0x1a,0x1a,0x1a,0x12, /* ` - g */
0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12, /* h - o */
0x12,0x12,0x12,0x12,0x12,0x12,0x12,0x12, /* p - w */
0x12,0x12,0x12,0x80,0x80,0x00,0x00,0x00, /* x -127 */
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 128-135 */
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 136-143 */
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 144-151 */
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 152-159 */
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 160-167 */
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 168-175 */
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 176-183 */
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 184-191 */
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 192-199 */
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 200-207 */
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 208-215 */
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 216-223 */
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 224-231 */
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 232-239 */
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, /* 240-247 */
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};/* 248-255 */
/* End of pcre_chartables.c */
Subproject commit 0c3cf8beb3a9309603c595a047d89c366d3cd383 Subproject commit 6040be9d0af812382934defe317c2e28434494ad
@ECHO OFF
SET DIR=%CD%
SET DEPS=%DIR%\.deps
RD /SQ %DEPS%
RD /SQ %DIR%\go\bin
RD /SQ %DIR%\bin
ECHO "Successfully clean overture"
...@@ -12,7 +12,7 @@ TARGET=$DIR/bin ...@@ -12,7 +12,7 @@ TARGET=$DIR/bin
DEPS=$DIR/.deps DEPS=$DIR/.deps
ANDROID_ARM_TOOLCHAIN=$DEPS/android-toolchain-${MIN_API}-arm 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_X86_TOOLCHAIN=$DEPS/android-toolchain-${MIN_API}-x86
ANDROID_ARM_CC=$ANDROID_ARM_TOOLCHAIN/bin/arm-linux-androideabi-clang ANDROID_ARM_CC=$ANDROID_ARM_TOOLCHAIN/bin/arm-linux-androideabi-clang
...@@ -35,7 +35,7 @@ fi ...@@ -35,7 +35,7 @@ fi
if [ ! -f "$ANDROID_ARM64_CC" ]; then if [ ! -f "$ANDROID_ARM64_CC" ]; then
echo "Make standalone toolchain for ARM64 arch" echo "Make standalone toolchain for ARM64 arch"
$ANDROID_NDK_HOME/build/tools/make_standalone_toolchain.py --arch arm64 \ $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 fi
if [ ! -f "$ANDROID_X86_CC" ]; then if [ ! -f "$ANDROID_X86_CC" ]; then
...@@ -44,7 +44,7 @@ if [ ! -f "$ANDROID_X86_CC" ]; then ...@@ -44,7 +44,7 @@ if [ ! -f "$ANDROID_X86_CC" ]; then
--api $MIN_API --install-dir $ANDROID_X86_TOOLCHAIN --api $MIN_API --install-dir $ANDROID_X86_TOOLCHAIN
fi fi
if [ ! -d "$DIR/go/bin" ]; then if [ ! -f "$DIR/go/bin/go" ]; then
echo "Build the custom go" echo "Build the custom go"
pushd $DIR/go/src pushd $DIR/go/src
...@@ -56,33 +56,38 @@ export GOROOT=$DIR/go ...@@ -56,33 +56,38 @@ export GOROOT=$DIR/go
export GOPATH=$DIR export GOPATH=$DIR
export PATH=$GOROOT/bin:$GOPATH/bin:$PATH export PATH=$GOROOT/bin:$GOPATH/bin:$PATH
echo "Get dependences for overture" if [ ! -f "$TARGET/armeabi-v7a/liboverture.so" ] || [ ! -f "$TARGET/arm64-v8a/liboverture.so" ] ||
go get -u github.com/tools/godep [ ! -f "$TARGET/x86/liboverture.so" ]; then
pushd $GOPATH/src/github.com/shadowsocks/overture/main echo "Get dependences for overture"
godep restore go get -u github.com/tools/godep
echo "Cross compile overture for arm" pushd $GOPATH/src/github.com/shadowsocks/overture/main
if [ ! -f "$TARGET/armeabi-v7a/liboverture.so" ]; then godep restore
echo "Cross compile overture for arm"
if [ ! -f "$TARGET/armeabi-v7a/liboverture.so" ]; then
try env CGO_ENABLED=1 CC=$ANDROID_ARM_CC GOOS=android GOARCH=arm GOARM=7 go build -ldflags="-s -w" try env CGO_ENABLED=1 CC=$ANDROID_ARM_CC GOOS=android GOARCH=arm GOARM=7 go build -ldflags="-s -w"
try $ANDROID_ARM_STRIP main try $ANDROID_ARM_STRIP main
try mv main $TARGET/armeabi-v7a/liboverture.so try mv main $TARGET/armeabi-v7a/liboverture.so
fi fi
echo "Cross compile overture for arm64" echo "Cross compile overture for arm64"
if [ ! -f "$TARGET/arm64-v8a/liboverture.so" ]; then if [ ! -f "$TARGET/arm64-v8a/liboverture.so" ]; then
try env CGO_ENABLED=1 CC=$ANDROID_ARM64_CC GOOS=android GOARCH=arm64 go build -ldflags="-s -w" try env CGO_ENABLED=1 CC=$ANDROID_ARM64_CC GOOS=android GOARCH=arm64 go build -ldflags="-s -w"
try $ANDROID_ARM64_STRIP main try $ANDROID_ARM64_STRIP main
try mv main $TARGET/arm64-v8a/liboverture.so try mv main $TARGET/arm64-v8a/liboverture.so
fi fi
echo "Cross compile overture for x86" echo "Cross compile overture for x86"
if [ ! -f "$TARGET/x86/liboverture.so" ]; then if [ ! -f "$TARGET/x86/liboverture.so" ]; then
try env CGO_ENABLED=1 CC=$ANDROID_X86_CC GOOS=android GOARCH=386 go build -ldflags="-s -w" try env CGO_ENABLED=1 CC=$ANDROID_X86_CC GOOS=android GOARCH=386 go build -ldflags="-s -w"
try $ANDROID_X86_STRIP main try $ANDROID_X86_STRIP main
try mv main $TARGET/x86/liboverture.so try mv main $TARGET/x86/liboverture.so
fi fi
popd popd
fi
echo "Successfully build overture" echo "Successfully build overture"
@ECHO OFF
SETLOCAL
IF NOT DEFINED ANDROID_NDK_HOME (
SET ANDROID_NDK_HOME=%ANDROID_HOME%\ndk-bundle
)
SET DIR=%CD%
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 c8ec480cf716be70bc3cae2c4cd7f2733901ab09 Subproject commit 466f9902c9779b8801f521f88db1d1522c880c26
package com.github.shadowsocks;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() throws Exception {
assertEquals(4, 2 + 2);
}
}
\ No newline at end of file
#Fri Dec 01 17:03:17 PST 2017
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-4.4.1-all.zip
#!/usr/bin/env bash #!/usr/bin/env sh
############################################################################## ##############################################################################
## ##
...@@ -6,20 +6,38 @@ ...@@ -6,20 +6,38 @@
## ##
############################################################################## ##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. # Attempt to set APP_HOME
DEFAULT_JVM_OPTS="" # Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle" APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"` APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum" MAX_FD="maximum"
warn ( ) { warn () {
echo "$*" echo "$*"
} }
die ( ) { die () {
echo echo
echo "$*" echo "$*"
echo echo
...@@ -30,6 +48,7 @@ die ( ) { ...@@ -30,6 +48,7 @@ die ( ) {
cygwin=false cygwin=false
msys=false msys=false
darwin=false darwin=false
nonstop=false
case "`uname`" in case "`uname`" in
CYGWIN* ) CYGWIN* )
cygwin=true cygwin=true
...@@ -40,26 +59,11 @@ case "`uname`" in ...@@ -40,26 +59,11 @@ case "`uname`" in
MINGW* ) MINGW* )
msys=true msys=true
;; ;;
NONSTOP* )
nonstop=true
;;
esac esac
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM. # Determine the Java command to use to start the JVM.
...@@ -85,7 +89,7 @@ location of your Java installation." ...@@ -85,7 +89,7 @@ location of your Java installation."
fi fi
# Increase the maximum file descriptors if we can. # Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n` MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
...@@ -150,11 +154,19 @@ if $cygwin ; then ...@@ -150,11 +154,19 @@ if $cygwin ; then
esac esac
fi fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules # Escape application args
function splitJvmOpts() { save () {
JVM_OPTS=("$@") for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
} }
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS APP_ARGS=$(save "$@")
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" exec "$JAVACMD" "$@"
...@@ -8,14 +8,14 @@ ...@@ -8,14 +8,14 @@
@rem Set local scope for the variables with windows NT shell @rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0 set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=. if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0 set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME% set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe @rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome if defined JAVA_HOME goto findJavaFromJavaHome
...@@ -46,10 +46,9 @@ echo location of your Java installation. ...@@ -46,10 +46,9 @@ echo location of your Java installation.
goto fail goto fail
:init :init
@rem Get command-line arguments, handling Windowz variants @rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args :win9xME_args
@rem Slurp the command line arguments. @rem Slurp the command line arguments.
...@@ -60,11 +59,6 @@ set _SKIP=2 ...@@ -60,11 +59,6 @@ set _SKIP=2
if "x%~1" == "x" goto execute if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%* set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute :execute
@rem Setup the command line @rem Setup the command line
......
...@@ -6,25 +6,26 @@ apply plugin: 'com.android.application' ...@@ -6,25 +6,26 @@ apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
def getCurrentFlavor() { def getCurrentFlavor() {
Matcher matcher = Pattern.compile("(assemble|generate)(\\w+)(Release|Debug)") String task = getGradle().getStartParameter().getTaskRequests().toString()
.matcher(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(1).toLowerCase() else {
println "NO MATCH FOUND" println "Warning: No match found for $task"
return "" return "debug"
} }
} }
android { android {
buildToolsVersion rootProject.buildToolsVersion
compileSdkVersion rootProject.sdkVersion compileSdkVersion rootProject.sdkVersion
defaultConfig { defaultConfig {
applicationId "com.github.shadowsocks" applicationId "com.github.shadowsocks"
minSdkVersion rootProject.minSdkVersion minSdkVersion rootProject.minSdkVersion
targetSdkVersion rootProject.sdkVersion targetSdkVersion rootProject.sdkVersion
versionCode 1 versionCode 1
versionName "4.4.0" versionName "4.4.5"
testApplicationId "com.github.shadowsocks.test"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary true resConfigs "fa", "fr", "ja", "ko", "ru", "zh-rCN", "zh-rTW"
resConfigs "fa", "ja", "ko", "ru", "zh-rCN", "zh-rTW"
} }
buildTypes { buildTypes {
debug { debug {
...@@ -36,16 +37,12 @@ android { ...@@ -36,16 +37,12 @@ android {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
} }
} }
lintOptions { lintOptions.checkReleaseBuilds false
abortOnError false
checkReleaseBuilds false
}
splits { splits {
abi { abi {
enable true enable true
reset() reset()
include 'armeabi-v7a', 'arm64-v8a', 'x86' include 'armeabi-v7a', 'arm64-v8a', 'x86'
universalApk false
} }
} }
sourceSets.main.jniLibs.srcDirs += sourceSets.main.jniLibs.srcDirs +=
...@@ -59,18 +56,13 @@ dependencies { ...@@ -59,18 +56,13 @@ dependencies {
implementation "com.android.support:design:$supportLibraryVersion" implementation "com.android.support:design:$supportLibraryVersion"
implementation "com.android.support:gridlayout-v7:$supportLibraryVersion" implementation "com.android.support:gridlayout-v7:$supportLibraryVersion"
implementation 'com.futuremind.recyclerfastscroll:fastscroll:0.2.5' 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.github.jorgecastilloprz:fabprogresscircle:1.01'
implementation "com.google.android.gms:play-services-ads:$playServicesVersion" 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-analytics:$playServicesVersion"
implementation "com.google.android.gms:play-services-gcm:$playServicesVersion"
implementation "com.google.firebase:firebase-config:$playServicesVersion" implementation "com.google.firebase:firebase-config:$playServicesVersion"
implementation 'com.j256.ormlite:ormlite-android:5.0' implementation 'com.j256.ormlite:ormlite-android:5.0'
implementation 'com.mikepenz:crossfader:1.5.1' 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:materialdrawer:6.0.2'
implementation 'com.mikepenz:materialize:1.1.2'
implementation 'com.squareup.okhttp3:okhttp:3.9.1' implementation 'com.squareup.okhttp3:okhttp:3.9.1'
implementation "com.takisoft.fix:preference-v7-simplemenu:$takisoftFixVersion" implementation "com.takisoft.fix:preference-v7-simplemenu:$takisoftFixVersion"
implementation 'com.twofortyfouram:android-plugin-api-for-locale:1.0.2' implementation 'com.twofortyfouram:android-plugin-api-for-locale:1.0.2'
......
<?xml version="1.0" encoding="UTF-8"?>
<lint>
<issue id="InvalidPackage">
<ignore regexp="ormlite-core" />
</issue>
<issue id="ImpliedQuantity" severity="warning" />
<issue id="ExtraTranslation" severity="warning" />
<issue id="MissingTranslation" severity="informational" />
</lint>
package com.github.shadowsocks;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() throws Exception {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();
assertEquals("com.github.shadowsocks", appContext.getPackageName());
}
}
/*******************************************************************************
* *
* 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.acl
import org.junit.Assert
import org.junit.Test
class AclTest {
companion object {
private const val INPUT1 = """[proxy_all]
[bypass_list]
1.0.1.0/24
(^|\.)4tern\.com${'$'}
"""
}
@Test
fun parse() {
Assert.assertEquals(INPUT1, Acl().fromReader(INPUT1.reader()).toString());
}
}
/*******************************************************************************
* *
* 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.database
import android.net.Uri
import org.junit.Assert
import org.junit.Test
class ProfileTest {
@Test
fun parsing() {
val results = Profile.findAll("garble ss://YmYtY2ZiOnRlc3RAMTkyLjE2OC4xMDAuMTo4ODg4#example-server garble")
.toList()
Assert.assertEquals(1, results.size)
Assert.assertEquals(Uri.parse("ss://YmYtY2ZiOnRlc3Q@192.168.100.1:8888#example-server"),
results.single().toUri())
}
}
package com.github.shadowsocks.utils package com.github.shadowsocks.utils
import com.github.shadowsocks.utils.Subnet
import org.junit.Assert import org.junit.Assert
import org.junit.Test import org.junit.Test
import java.net.InetAddress import java.net.InetAddress
object SubnetTest { class SubnetTest {
@Test @Test
fun parsingAndEquals() { fun parsingAndEquals() {
Assert.assertEquals(Subnet(InetAddress.getByName("1.10.11.12"), 25), Subnet.fromString("1.10.11.12/25")) Assert.assertEquals(Subnet(InetAddress.getByName("1.10.11.12"), 25), Subnet.fromString("1.10.11.12/25"))
...@@ -17,16 +16,13 @@ object SubnetTest { ...@@ -17,16 +16,13 @@ object SubnetTest {
Assert.assertNotEquals(Subnet.fromString("1.2.3.4/12"), Subnet.fromString("1.2.3.5/12")) Assert.assertNotEquals(Subnet.fromString("1.2.3.4/12"), Subnet.fromString("1.2.3.5/12"))
} }
@Test(expected = IllegalArgumentException::class) @Test
fun invalidParsing1() { fun invalidParsings() {
Subnet.fromString("caec:cec6:c4ef:bb7b:1a78:d055:216d:3a78/129") Assert.assertEquals(null, Subnet.fromString("1.2.3.456"))
} Assert.assertEquals(null, Subnet.fromString("ffff::f0000"))
@Test(expected = IllegalArgumentException::class) Assert.assertEquals(null, Subnet.fromString("caec:cec6:c4ef:bb7b:1a78:d055:216d:3a78/129"))
fun invalidParsing2() { Assert.assertEquals(null, Subnet.fromString("caec:cec6:c4ef:bb7b:1a78:d055:216d:3a78/129"))
Subnet.fromString("caec:cec6:c4ef:bb7b:1a78:d055:216d:3a78/-99") Assert.assertEquals(null, Subnet.fromString("caec:cec6:c4ef:bb7b:1a78:d055:216d:3a78/-99"))
} Assert.assertEquals(null, Subnet.fromString("caec:cec6:c4ef:bb7b:1a78:d055:216d:3a78/1/0"))
@Test(expected = IllegalArgumentException::class)
fun invalidParsing3() {
Subnet.fromString("caec:cec6:c4ef:bb7b:1a78:d055:216d:3a78/1/0")
} }
} }
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
package="com.github.shadowsocks"> package="com.github.shadowsocks"
android:installLocation="internalOnly">
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
...@@ -19,11 +20,14 @@ ...@@ -19,11 +20,14 @@
<application <application
android:name=".App" android:name=".App"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:allowBackup="true"
android:backupAgent=".ConfigBackupHelper" android:backupAgent=".ConfigBackupHelper"
android:fullBackupContent="@xml/backup_descriptor"
android:fullBackupOnly="true"
android:label="@string/app_name" android:label="@string/app_name"
android:supportsRtl="true" android:supportsRtl="true"
android:networkSecurityConfig="@xml/network_security_config" android:networkSecurityConfig="@xml/network_security_config"
android:banner="@drawable/ic_start_connected"> android:banner="@drawable/ic_service_active">
<meta-data android:name="android.webkit.WebView.EnableSafeBrowsing" <meta-data android:name="android.webkit.WebView.EnableSafeBrowsing"
android:value="true" /> android:value="true" />
...@@ -31,9 +35,6 @@ ...@@ -31,9 +35,6 @@
android:value="@integer/google_play_services_version"/> android:value="@integer/google_play_services_version"/>
<meta-data android:name="com.google.android.backup.api_key" <meta-data android:name="com.google.android.backup.api_key"
android:value="AEdPqrEAAAAI_zVxZthz2HDuz9toTvkYvL0L5GA-OjeUIfBeXg"/> android:value="AEdPqrEAAAAI_zVxZthz2HDuz9toTvkYvL0L5GA-OjeUIfBeXg"/>
<meta-data
android:name="preloaded_fonts"
android:resource="@array/preloaded_fonts"/>
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
...@@ -111,6 +112,7 @@ ...@@ -111,6 +112,7 @@
<service <service
android:name=".bg.VpnService" android:name=".bg.VpnService"
android:process=":bg" android:process=":bg"
android:directBootAware="true"
android:label="@string/app_name" android:label="@string/app_name"
android:permission="android.permission.BIND_VPN_SERVICE" android:permission="android.permission.BIND_VPN_SERVICE"
android:exported="false"> android:exported="false">
...@@ -122,31 +124,41 @@ ...@@ -122,31 +124,41 @@
<service <service
android:name=".bg.TransproxyService" android:name=".bg.TransproxyService"
android:process=":bg" android:process=":bg"
android:directBootAware="true"
android:exported="false"> android:exported="false">
</service> </service>
<service <service
android:name=".bg.ProxyService" android:name=".bg.ProxyService"
android:process=":bg" android:process=":bg"
android:directBootAware="true"
android:exported="false"> android:exported="false">
</service> </service>
<service android:name=".bg.TileService" android:label="@string/quick_toggle" <service android:name=".bg.TileService" android:label="@string/quick_toggle"
android:process=":bg" android:process=":bg"
android:icon="@drawable/ic_start_connected" android:directBootAware="true"
android:icon="@drawable/ic_service_active"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"> android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
<intent-filter> <intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE" /> <action android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter> </intent-filter>
</service> </service>
<receiver android:name=".BootReceiver" android:process=":bg" android:enabled="false"> <receiver android:name=".BootReceiver"
android:process=":bg"
android:directBootAware="true"
android:enabled="false">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/> <action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
</intent-filter> </intent-filter>
</receiver> </receiver>
<receiver android:name=".tasker.ActionListener" android:process=":bg" tools:ignore="ExportedReceiver"> <receiver android:name=".tasker.ActionListener"
android:process=":bg"
android:directBootAware="true"
tools:ignore="ExportedReceiver">
<intent-filter> <intent-filter>
<action android:name="com.twofortyfouram.locale.intent.action.FIRE_SETTING"/> <action android:name="com.twofortyfouram.locale.intent.action.FIRE_SETTING"/>
</intent-filter> </intent-filter>
...@@ -154,16 +166,24 @@ ...@@ -154,16 +166,24 @@
<!-- android-job components --> <!-- android-job components -->
<service android:name="com.evernote.android.job.v21.PlatformJobService" <service android:name="com.evernote.android.job.v21.PlatformJobService"
android:process=":bg"/> android:process=":bg"
android:directBootAware="true"/>
<service android:name="com.evernote.android.job.v14.PlatformAlarmService" <service android:name="com.evernote.android.job.v14.PlatformAlarmService"
android:process=":bg"/> android:process=":bg"
android:directBootAware="true"/>
<service android:name="com.evernote.android.job.v14.PlatformAlarmServiceExact"
android:process=":bg"
android:directBootAware="true"/>
<service android:name="com.evernote.android.job.gcm.PlatformGcmService" <service android:name="com.evernote.android.job.gcm.PlatformGcmService"
android:process=":bg"/> tools:node="remove"/>
<service android:name="com.evernote.android.job.JobRescheduleService" <service android:name="com.evernote.android.job.JobRescheduleService"
android:process=":bg"/> android:process=":bg"
android:directBootAware="true"/>
<receiver android:name="com.evernote.android.job.v14.PlatformAlarmReceiver" <receiver android:name="com.evernote.android.job.v14.PlatformAlarmReceiver"
android:process=":bg"/> android:process=":bg"
android:directBootAware="true"/>
<receiver android:name="com.evernote.android.job.JobBootReceiver" <receiver android:name="com.evernote.android.job.JobBootReceiver"
android:process=":bg"/> android:process=":bg"
android:directBootAware="true"/>
</application> </application>
</manifest> </manifest>
This diff is collapsed.
[proxy_all]
[bypass_list]
0.0.0.0/8 0.0.0.0/8
10.0.0.0/8 10.0.0.0/8
100.64.0.0/10 100.64.0.0/10
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
package android.support.design.widget
import android.view.animation.Interpolator
object SnackbarAnimation {
val FAST_OUT_SLOW_IN_INTERPOLATOR: Interpolator get() = AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR
const val ANIMATION_DURATION = BaseTransientBottomBar.ANIMATION_DURATION.toLong()
}
/*******************************************************************************
* *
* 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.evernote.android.job
object JobConstants {
const val DATABASE_NAME = JobStorage.DATABASE_NAME
const val PREF_FILE_NAME = JobStorage.PREF_FILE_NAME
}
...@@ -24,6 +24,7 @@ import android.app.Application ...@@ -24,6 +24,7 @@ import android.app.Application
import android.app.NotificationChannel import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.content.pm.PackageInfo import android.content.pm.PackageInfo
...@@ -31,12 +32,14 @@ import android.content.pm.PackageManager ...@@ -31,12 +32,14 @@ import android.content.pm.PackageManager
import android.content.res.Configuration import android.content.res.Configuration
import android.os.Build import android.os.Build
import android.os.Handler import android.os.Handler
import android.os.LocaleList
import android.os.Looper import android.os.Looper
import android.support.annotation.RequiresApi import android.support.annotation.RequiresApi
import android.support.v4.os.UserManagerCompat
import android.support.v7.app.AppCompatDelegate import android.support.v7.app.AppCompatDelegate
import android.util.Log import android.util.Log
import com.evernote.android.job.JobConstants
import com.evernote.android.job.JobManager import com.evernote.android.job.JobManager
import com.github.shadowsocks.acl.Acl
import com.github.shadowsocks.acl.AclSyncJob import com.github.shadowsocks.acl.AclSyncJob
import com.github.shadowsocks.bg.BaseService import com.github.shadowsocks.bg.BaseService
import com.github.shadowsocks.database.Profile import com.github.shadowsocks.database.Profile
...@@ -44,10 +47,7 @@ import com.github.shadowsocks.database.ProfileManager ...@@ -44,10 +47,7 @@ import com.github.shadowsocks.database.ProfileManager
import com.github.shadowsocks.preference.BottomSheetPreferenceDialogFragment import com.github.shadowsocks.preference.BottomSheetPreferenceDialogFragment
import com.github.shadowsocks.preference.DataStore import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.preference.IconListPreference import com.github.shadowsocks.preference.IconListPreference
import com.github.shadowsocks.utils.Action import com.github.shadowsocks.utils.*
import com.github.shadowsocks.utils.Key
import com.github.shadowsocks.utils.TcpFastOpen
import com.github.shadowsocks.utils.broadcastReceiver
import com.google.android.gms.analytics.GoogleAnalytics import com.google.android.gms.analytics.GoogleAnalytics
import com.google.android.gms.analytics.HitBuilders import com.google.android.gms.analytics.HitBuilders
import com.google.android.gms.analytics.StandardExceptionParser import com.google.android.gms.analytics.StandardExceptionParser
...@@ -58,25 +58,17 @@ import com.j256.ormlite.logger.LocalLog ...@@ -58,25 +58,17 @@ import com.j256.ormlite.logger.LocalLog
import com.takisoft.fix.support.v7.preference.PreferenceFragmentCompat import com.takisoft.fix.support.v7.preference.PreferenceFragmentCompat
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import java.util.*
class App : Application() { class App : Application() {
companion object { companion object {
lateinit var app: App lateinit var app: App
private const val TAG = "ShadowsocksApplication" 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()) } val handler by lazy { Handler(Looper.getMainLooper()) }
val deviceContext: Context by lazy { if (Build.VERSION.SDK_INT < 24) this else DeviceContext(this) }
val remoteConfig: FirebaseRemoteConfig by lazy { FirebaseRemoteConfig.getInstance() } val remoteConfig: FirebaseRemoteConfig by lazy { FirebaseRemoteConfig.getInstance() }
private val tracker: Tracker by lazy { GoogleAnalytics.getInstance(this).newTracker(R.xml.tracker) } private val tracker: Tracker by lazy { GoogleAnalytics.getInstance(deviceContext).newTracker(R.xml.tracker) }
val info: PackageInfo by lazy { packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES) } val info: PackageInfo by lazy { packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES) }
fun startService() { fun startService() {
...@@ -86,7 +78,8 @@ class App : Application() { ...@@ -86,7 +78,8 @@ class App : Application() {
fun reloadService() = sendBroadcast(Intent(Action.RELOAD)) fun reloadService() = sendBroadcast(Intent(Action.RELOAD))
fun stopService() = sendBroadcast(Intent(Action.CLOSE)) fun stopService() = sendBroadcast(Intent(Action.CLOSE))
val currentProfile: Profile? get() = ProfileManager.getProfile(DataStore.profileId) val currentProfile: Profile? get() =
if (DataStore.directBootAware) DirectBoot.getDeviceProfile() else ProfileManager.getProfile(DataStore.profileId)
fun switchProfile(id: Int): Profile { fun switchProfile(id: Int): Profile {
val result = ProfileManager.getProfile(id) ?: ProfileManager.createProfile() val result = ProfileManager.getProfile(id) ?: ProfileManager.createProfile()
...@@ -109,86 +102,58 @@ class App : Application() { ...@@ -109,86 +102,58 @@ class App : Application() {
t.printStackTrace() 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() { override fun onCreate() {
super.onCreate() super.onCreate()
app = this app = this
if (!BuildConfig.DEBUG) System.setProperty(LocalLog.LOCAL_LOG_LEVEL_PROPERTY, "ERROR") if (!BuildConfig.DEBUG) System.setProperty(LocalLog.LOCAL_LOG_LEVEL_PROPERTY, "ERROR")
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true) AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
checkChineseLocale(resources.configuration) PreferenceFragmentCompat.registerPreferenceFragment(IconListPreference::class.java,
BottomSheetPreferenceDialogFragment::class.java)
if (Build.VERSION.SDK_INT >= 24) { // migrate old files
deviceContext.moveDatabaseFrom(this, Key.DB_PUBLIC)
deviceContext.moveDatabaseFrom(this, JobConstants.DATABASE_NAME)
deviceContext.moveSharedPreferencesFrom(this, JobConstants.PREF_FILE_NAME)
val old = Acl.getFile(Acl.CUSTOM_RULES, this)
if (old.canRead()) {
Acl.getFile(Acl.CUSTOM_RULES).writeText(old.readText())
old.delete()
}
}
FirebaseApp.initializeApp(this) FirebaseApp.initializeApp(deviceContext)
remoteConfig.setDefaults(R.xml.default_configs) remoteConfig.setDefaults(R.xml.default_configs)
remoteConfig.fetch().addOnCompleteListener { remoteConfig.fetch().addOnCompleteListener {
if (it.isSuccessful) remoteConfig.activateFetched() else Log.e(TAG, "Failed to fetch config") if (it.isSuccessful) remoteConfig.activateFetched() else Log.e(TAG, "Failed to fetch config")
} }
JobManager.create(deviceContext).addJobCreator(AclSyncJob)
JobManager.create(this).addJobCreator(AclSyncJob) // handle data restored
PreferenceFragmentCompat.registerPreferenceFragment(IconListPreference::class.java, if (DataStore.directBootAware && UserManagerCompat.isUserUnlocked(this)) DirectBoot.update()
BottomSheetPreferenceDialogFragment::class.java) TcpFastOpen.enabled(DataStore.publicStore.getBoolean(Key.tfo, TcpFastOpen.sendEnabled))
if (DataStore.publicStore.getLong(Key.assetUpdateTime, -1) != info.lastUpdateTime) {
TcpFastOpen.enabled(DataStore.getBoolean(Key.tfo, TcpFastOpen.sendEnabled))
if (DataStore.getLong(Key.assetUpdateTime, -1) != info.lastUpdateTime) {
val assetManager = assets val assetManager = assets
for (dir in arrayOf("acl", "overture")) for (dir in arrayOf("acl", "overture"))
try { try {
for (file in assetManager.list(dir)) assetManager.open(dir + '/' + file).use { input -> for (file in assetManager.list(dir)) assetManager.open(dir + '/' + file).use { input ->
File(filesDir, file).outputStream().use { output -> input.copyTo(output) } File(deviceContext.filesDir, file).outputStream().use { output -> input.copyTo(output) }
} }
} catch (e: IOException) { } catch (e: IOException) {
Log.e(TAG, e.message) Log.e(TAG, e.message)
app.track(e) app.track(e)
} }
DataStore.putLong(Key.assetUpdateTime, info.lastUpdateTime) 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) { if (Build.VERSION.SDK_INT >= 26) @RequiresApi(26) {
val nm = getSystemService(NotificationManager::class.java) val nm = getSystemService(NotificationManager::class.java)
nm.createNotificationChannels(listOf( nm.createNotificationChannels(listOf(
...@@ -202,11 +167,6 @@ class App : Application() { ...@@ -202,11 +167,6 @@ class App : Application() {
} }
} }
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
checkChineseLocale(newConfig)
}
fun listenForPackageChanges(callback: () -> Unit): BroadcastReceiver { fun listenForPackageChanges(callback: () -> Unit): BroadcastReceiver {
val filter = IntentFilter(Intent.ACTION_PACKAGE_ADDED) val filter = IntentFilter(Intent.ACTION_PACKAGE_ADDED)
filter.addAction(Intent.ACTION_PACKAGE_REMOVED) filter.addAction(Intent.ACTION_PACKAGE_REMOVED)
......
...@@ -28,6 +28,8 @@ import android.content.BroadcastReceiver ...@@ -28,6 +28,8 @@ import android.content.BroadcastReceiver
import android.content.ClipData import android.content.ClipData
import android.content.ClipboardManager import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.os.Bundle import android.os.Bundle
...@@ -47,34 +49,39 @@ import com.futuremind.recyclerviewfastscroll.SectionTitleProvider ...@@ -47,34 +49,39 @@ import com.futuremind.recyclerviewfastscroll.SectionTitleProvider
import com.github.shadowsocks.App.Companion.app import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.database.ProfileManager import com.github.shadowsocks.database.ProfileManager
import com.github.shadowsocks.preference.DataStore import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.DirectBoot
import com.github.shadowsocks.utils.Key import com.github.shadowsocks.utils.Key
import com.github.shadowsocks.utils.resolveResourceId
import com.github.shadowsocks.utils.thread import com.github.shadowsocks.utils.thread
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
class AppManager : AppCompatActivity(), Toolbar.OnMenuItemClickListener { class AppManager : AppCompatActivity(), Toolbar.OnMenuItemClickListener {
companion object { companion object {
data class ProxiedApp(val name: String, val packageName: String, val icon: Drawable)
@SuppressLint("StaticFieldLeak") @SuppressLint("StaticFieldLeak")
private var instance: AppManager? = null private var instance: AppManager? = null
private var receiver: BroadcastReceiver? = null private var receiver: BroadcastReceiver? = null
private var cachedApps: Array<ProxiedApp>? = null private var cachedApps: List<PackageInfo>? = null
private fun getApps(pm: PackageManager): Array<ProxiedApp> { private fun getApps(pm: PackageManager): List<ProxiedApp> {
if (receiver == null) receiver = app.listenForPackageChanges { if (receiver == null) receiver = app.listenForPackageChanges {
synchronized(AppManager) { cachedApps = null } synchronized(AppManager) { cachedApps = null }
AppManager.instance?.reloadApps() AppManager.instance?.reloadApps()
} }
return synchronized(AppManager) { return synchronized(AppManager) {
// Labels and icons can change on configuration (locale, etc.) changes, therefore they are not cached.
val cachedApps = cachedApps ?: pm.getInstalledPackages(PackageManager.GET_PERMISSIONS) val cachedApps = cachedApps ?: pm.getInstalledPackages(PackageManager.GET_PERMISSIONS)
.filter { it.requestedPermissions?.contains(Manifest.permission.INTERNET) ?: false } .filter { it.packageName != app.packageName &&
.map { ProxiedApp(pm.getApplicationLabel(it.applicationInfo).toString(), it.packageName, it.requestedPermissions?.contains(Manifest.permission.INTERNET) ?: false }
it.applicationInfo.loadIcon(pm)) }
.toTypedArray()
this.cachedApps = cachedApps this.cachedApps = cachedApps
cachedApps cachedApps
}.map { ProxiedApp(pm, it.applicationInfo, it.packageName) }
} }
} }
private class ProxiedApp(private val pm: PackageManager, private val appInfo: ApplicationInfo,
val packageName: String) {
val name: CharSequence = appInfo.loadLabel(pm) // cached for sorting
val icon: Drawable get() = appInfo.loadIcon(pm)
} }
private inner class AppViewHolder(view: View) : RecyclerView.ViewHolder(view), View.OnClickListener { private inner class AppViewHolder(view: View) : RecyclerView.ViewHolder(view), View.OnClickListener {
...@@ -113,13 +120,8 @@ class AppManager : AppCompatActivity(), Toolbar.OnMenuItemClickListener { ...@@ -113,13 +120,8 @@ class AppManager : AppCompatActivity(), Toolbar.OnMenuItemClickListener {
private var apps = listOf<ProxiedApp>() private var apps = listOf<ProxiedApp>()
fun reload() { fun reload() {
apps = getApps(packageManager).sortedWith(Comparator { a, b -> apps = getApps(packageManager)
when (Pair(proxiedApps.contains(a.packageName), proxiedApps.contains(b.packageName))) { .sortedWith(compareBy({ !proxiedApps.contains(it.packageName) }, { it.name.toString() }))
Pair(true, false) -> -1
Pair(false, true) -> 1
else -> a.name.compareTo(b.name, true)
}
})
} }
override fun onBindViewHolder(holder: AppViewHolder, position: Int) = holder.bind(apps[position]) override fun onBindViewHolder(holder: AppViewHolder, position: Int) = holder.bind(apps[position])
...@@ -179,7 +181,7 @@ class AppManager : AppCompatActivity(), Toolbar.OnMenuItemClickListener { ...@@ -179,7 +181,7 @@ class AppManager : AppCompatActivity(), Toolbar.OnMenuItemClickListener {
setContentView(R.layout.layout_apps) setContentView(R.layout.layout_apps)
toolbar = findViewById(R.id.toolbar) toolbar = findViewById(R.id.toolbar)
toolbar.setTitle(R.string.proxied_apps) toolbar.setTitle(R.string.proxied_apps)
toolbar.setNavigationIcon(R.drawable.abc_ic_ab_back_material) toolbar.setNavigationIcon(theme.resolveResourceId(R.attr.homeAsUpIndicator))
toolbar.setNavigationOnClickListener { toolbar.setNavigationOnClickListener {
val intent = parentActivityIntent val intent = parentActivityIntent
if (shouldUpRecreateTask(intent) || isTaskRoot) if (shouldUpRecreateTask(intent) || isTaskRoot)
...@@ -228,6 +230,7 @@ class AppManager : AppCompatActivity(), Toolbar.OnMenuItemClickListener { ...@@ -228,6 +230,7 @@ class AppManager : AppCompatActivity(), Toolbar.OnMenuItemClickListener {
it.individual = proxiedAppString it.individual = proxiedAppString
ProfileManager.updateProfile(it) ProfileManager.updateProfile(it)
} }
if (DataStore.directBootAware) DirectBoot.update()
Toast.makeText(this, R.string.action_apply_all, Toast.LENGTH_SHORT).show() Toast.makeText(this, R.string.action_apply_all, Toast.LENGTH_SHORT).show()
} else Toast.makeText(this, R.string.action_export_err, Toast.LENGTH_SHORT).show() } else Toast.makeText(this, R.string.action_export_err, Toast.LENGTH_SHORT).show()
return true return true
......
...@@ -26,6 +26,7 @@ import android.content.Context ...@@ -26,6 +26,7 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import com.github.shadowsocks.App.Companion.app import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.preference.DataStore
class BootReceiver : BroadcastReceiver() { class BootReceiver : BroadcastReceiver() {
companion object { companion object {
...@@ -39,6 +40,11 @@ class BootReceiver : BroadcastReceiver() { ...@@ -39,6 +40,11 @@ class BootReceiver : BroadcastReceiver() {
} }
override fun onReceive(context: Context, intent: Intent) { override fun onReceive(context: Context, intent: Intent) {
if (intent.action == Intent.ACTION_BOOT_COMPLETED) app.startService() val locked = when (intent.action) {
Intent.ACTION_BOOT_COMPLETED -> false
Intent.ACTION_LOCKED_BOOT_COMPLETED -> true // constant will be folded so no need to do version checks
else -> return
}
if (DataStore.directBootAware == locked) app.startService()
} }
} }
...@@ -22,10 +22,10 @@ package com.github.shadowsocks ...@@ -22,10 +22,10 @@ package com.github.shadowsocks
import android.app.backup.BackupAgentHelper import android.app.backup.BackupAgentHelper
import android.app.backup.FileBackupHelper import android.app.backup.FileBackupHelper
import com.github.shadowsocks.acl.Acl
import com.github.shadowsocks.utils.Key import com.github.shadowsocks.utils.Key
@Deprecated("Only used in API level < 23. For 6.0+, Auto Backup for Apps is used.")
class ConfigBackupHelper : BackupAgentHelper() { class ConfigBackupHelper : BackupAgentHelper() {
override fun onCreate() = addHelper("com.github.shadowsocks.database.profile", override fun onCreate() = addHelper("com.github.shadowsocks.database.profile", FileBackupHelper(this,
FileBackupHelper(this, "../databases/" + Key.PROFILE, Acl.CUSTOM_RULES + ".acl")) "../databases/" + Key.DB_PROFILE, "../databases/" + Key.DB_PUBLIC))
} }
...@@ -20,27 +20,40 @@ ...@@ -20,27 +20,40 @@
package com.github.shadowsocks package com.github.shadowsocks
import android.app.admin.DevicePolicyManager
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.support.design.widget.Snackbar import android.support.design.widget.Snackbar
import android.support.v14.preference.SwitchPreference import android.support.v14.preference.SwitchPreference
import android.support.v7.preference.Preference import android.support.v7.preference.Preference
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.bg.BaseService import com.github.shadowsocks.bg.BaseService
import com.github.shadowsocks.preference.DataStore import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.DirectBoot
import com.github.shadowsocks.utils.Key import com.github.shadowsocks.utils.Key
import com.github.shadowsocks.utils.TcpFastOpen import com.github.shadowsocks.utils.TcpFastOpen
import com.takisoft.fix.support.v7.preference.PreferenceFragmentCompatDividers import com.takisoft.fix.support.v7.preference.PreferenceFragmentCompatDividers
class GlobalSettingsPreferenceFragment : PreferenceFragmentCompatDividers() { class GlobalSettingsPreferenceFragment : PreferenceFragmentCompatDividers() {
override fun onCreatePreferencesFix(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferencesFix(savedInstanceState: Bundle?, rootKey: String?) {
preferenceManager.preferenceDataStore = DataStore preferenceManager.preferenceDataStore = DataStore.publicStore
DataStore.initGlobal() DataStore.initGlobal()
addPreferencesFromResource(R.xml.pref_global) addPreferencesFromResource(R.xml.pref_global)
val switch = findPreference(Key.isAutoConnect) as SwitchPreference val boot = findPreference(Key.isAutoConnect) as SwitchPreference
switch.setOnPreferenceChangeListener { _, value -> val directBootAwareListener = Preference.OnPreferenceChangeListener { _, newValue ->
BootReceiver.enabled = value as Boolean if (newValue as Boolean) DirectBoot.update() else DirectBoot.clean()
true true
} }
switch.isChecked = BootReceiver.enabled boot.setOnPreferenceChangeListener { _, value ->
BootReceiver.enabled = value as Boolean
directBootAwareListener.onPreferenceChange(null, DataStore.directBootAware)
}
boot.isChecked = BootReceiver.enabled
val dba = findPreference(Key.directBootAware)
if (Build.VERSION.SDK_INT >= 24 && context!!.getSystemService(DevicePolicyManager::class.java)
.storageEncryptionStatus == DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER)
dba.onPreferenceChangeListener = directBootAwareListener else dba.parent!!.removePreference(dba)
val tfo = findPreference(Key.tfo) as SwitchPreference val tfo = findPreference(Key.tfo) as SwitchPreference
tfo.isChecked = TcpFastOpen.sendEnabled tfo.isChecked = TcpFastOpen.sendEnabled
......
...@@ -31,19 +31,15 @@ import android.nfc.NfcAdapter ...@@ -31,19 +31,15 @@ import android.nfc.NfcAdapter
import android.os.Bundle import android.os.Bundle
import android.os.SystemClock import android.os.SystemClock
import android.support.customtabs.CustomTabsIntent import android.support.customtabs.CustomTabsIntent
import android.support.design.widget.FloatingActionButton
import android.support.design.widget.Snackbar import android.support.design.widget.Snackbar
import android.support.v4.content.ContextCompat import android.support.v4.content.ContextCompat
import android.support.v7.app.AlertDialog import android.support.v7.app.AlertDialog
import android.support.v7.app.AppCompatActivity import android.support.v7.app.AppCompatActivity
import android.support.v7.content.res.AppCompatResources import android.support.v7.content.res.AppCompatResources
import android.support.v7.preference.PreferenceDataStore import android.support.v7.preference.PreferenceDataStore
import android.support.v7.widget.TooltipCompat
import android.text.TextUtils
import android.util.Log import android.util.Log
import android.view.View import android.view.View
import android.widget.TextView import android.widget.TextView
import com.github.jorgecastilloprz.FABProgressCircle
import com.github.shadowsocks.App.Companion.app import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.acl.Acl import com.github.shadowsocks.acl.Acl
import com.github.shadowsocks.acl.CustomRulesFragment import com.github.shadowsocks.acl.CustomRulesFragment
...@@ -59,6 +55,7 @@ import com.github.shadowsocks.preference.OnPreferenceDataStoreChangeListener ...@@ -59,6 +55,7 @@ import com.github.shadowsocks.preference.OnPreferenceDataStoreChangeListener
import com.github.shadowsocks.utils.Key import com.github.shadowsocks.utils.Key
import com.github.shadowsocks.utils.responseLength import com.github.shadowsocks.utils.responseLength
import com.github.shadowsocks.utils.thread import com.github.shadowsocks.utils.thread
import com.github.shadowsocks.widget.ServiceButton
import com.mikepenz.crossfader.Crossfader import com.mikepenz.crossfader.Crossfader
import com.mikepenz.crossfader.view.CrossFadeSlidingPaneLayout import com.mikepenz.crossfader.view.CrossFadeSlidingPaneLayout
import com.mikepenz.materialdrawer.Drawer import com.mikepenz.materialdrawer.Drawer
...@@ -88,8 +85,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, Drawe ...@@ -88,8 +85,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, Drawe
} }
// UI // UI
private lateinit var fab: FloatingActionButton private lateinit var fab: ServiceButton
private lateinit var fabProgressCircle: FABProgressCircle
internal var crossfader: Crossfader<CrossFadeSlidingPaneLayout>? = null internal var crossfader: Crossfader<CrossFadeSlidingPaneLayout>? = null
internal lateinit var drawer: Drawer internal lateinit var drawer: Drawer
private var previousSelectedDrawer: Long = 0 // it's actually lateinit private var previousSelectedDrawer: Long = 0 // it's actually lateinit
...@@ -111,19 +107,12 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, Drawe ...@@ -111,19 +107,12 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, Drawe
} catch (_: ActivityNotFoundException) { } // ignore } catch (_: ActivityNotFoundException) { } // ignore
fun launchUrl(uri: String) = launchUrl(Uri.parse(uri)) fun launchUrl(uri: String) = launchUrl(Uri.parse(uri))
private val greyTint by lazy { ContextCompat.getColorStateList(this, R.color.material_primary_500) }
private val greenTint by lazy { ContextCompat.getColorStateList(this, R.color.material_green_700) }
private fun hideCircle() = try {
fabProgressCircle.hide()
} catch (_: NullPointerException) { } // ignore
// service // service
var state = BaseService.IDLE var state = BaseService.IDLE
override val serviceCallback: IShadowsocksServiceCallback.Stub by lazy { override val serviceCallback: IShadowsocksServiceCallback.Stub by lazy {
object : IShadowsocksServiceCallback.Stub() { object : IShadowsocksServiceCallback.Stub() {
override fun stateChanged(state: Int, profileName: String?, msg: String?) { override fun stateChanged(state: Int, profileName: String?, msg: String?) {
app.handler.post { changeState(state, msg) } app.handler.post { changeState(state, msg, true) }
} }
override fun trafficUpdated(profileId: Int, txRate: Long, rxRate: Long, txTotal: Long, rxTotal: Long) { override fun trafficUpdated(profileId: Int, txRate: Long, rxRate: Long, txTotal: Long, rxTotal: Long) {
app.handler.post { updateTraffic(profileId, txRate, rxRate, txTotal, rxTotal) } app.handler.post { updateTraffic(profileId, txRate, rxRate, txTotal, rxTotal) }
...@@ -134,27 +123,13 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, Drawe ...@@ -134,27 +123,13 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, Drawe
} }
} }
fun changeState(state: Int, msg: String? = null) { fun changeState(state: Int, msg: String? = null, animate: Boolean = false) {
fab.changeState(state, animate)
when (state) { when (state) {
BaseService.CONNECTING -> { BaseService.CONNECTING -> statusText.setText(R.string.connecting)
fab.setImageResource(R.drawable.ic_start_busy) BaseService.CONNECTED -> statusText.setText(R.string.vpn_connected)
fabProgressCircle.show() BaseService.STOPPING -> statusText.setText(R.string.stopping)
statusText.setText(R.string.connecting)
}
BaseService.CONNECTED -> {
if (this.state == BaseService.CONNECTING) fabProgressCircle.beginFinalAnimation()
else fabProgressCircle.postDelayed(this::hideCircle, 1000)
fab.setImageResource(R.drawable.ic_start_connected)
statusText.setText(R.string.vpn_connected)
}
BaseService.STOPPING -> {
fab.setImageResource(R.drawable.ic_start_busy)
if (this.state == BaseService.CONNECTED) fabProgressCircle.show() // ignore for stopped
statusText.setText(R.string.stopping)
}
else -> { else -> {
fab.setImageResource(R.drawable.ic_start_idle)
fabProgressCircle.postDelayed(this::hideCircle, 1000)
if (msg != null) { if (msg != null) {
Snackbar.make(findViewById(R.id.snackbar), Snackbar.make(findViewById(R.id.snackbar),
getString(R.string.vpn_error).format(Locale.ENGLISH, msg), Snackbar.LENGTH_LONG).show() getString(R.string.vpn_error).format(Locale.ENGLISH, msg), Snackbar.LENGTH_LONG).show()
...@@ -164,20 +139,12 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, Drawe ...@@ -164,20 +139,12 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, Drawe
} }
} }
this.state = state this.state = state
if (state == BaseService.CONNECTED) { if (state != BaseService.CONNECTED) {
fab.backgroundTintList = greenTint
TooltipCompat.setTooltipText(fab, getString(R.string.stop))
} else {
fab.backgroundTintList = greyTint
TooltipCompat.setTooltipText(fab, getString(R.string.connect))
updateTraffic(-1, 0, 0, 0, 0) updateTraffic(-1, 0, 0, 0, 0)
testCount += 1 // suppress previous test messages testCount += 1 // suppress previous test messages
} }
ProfilesFragment.instance?.profilesAdapter?.notifyDataSetChanged() // refresh button enabled state ProfilesFragment.instance?.profilesAdapter?.notifyDataSetChanged() // refresh button enabled state
stateListener?.invoke(state) stateListener?.invoke(state)
fab.isEnabled = false
if (state == BaseService.CONNECTED || state == BaseService.STOPPED) app.handler.postDelayed(
{ fab.isEnabled = state == BaseService.CONNECTED || state == BaseService.STOPPED }, 1000)
} }
fun updateTraffic(profileId: Int, txRate: Long, rxRate: Long, txTotal: Long, rxTotal: Long) { fun updateTraffic(profileId: Int, txRate: Long, rxRate: Long, txTotal: Long, rxTotal: Long) {
txText.text = TrafficMonitor.formatTraffic(txTotal) txText.text = TrafficMonitor.formatTraffic(txTotal)
...@@ -225,6 +192,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, Drawe ...@@ -225,6 +192,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, Drawe
} }
} }
override val listenForDeath: Boolean get() = true
override fun onServiceConnected(service: IShadowsocksService) = changeState(service.state) override fun onServiceConnected(service: IShadowsocksService) = changeState(service.state)
override fun onServiceDisconnected() = changeState(BaseService.IDLE) override fun onServiceDisconnected() = changeState(BaseService.IDLE)
override fun binderDied() { override fun binderDied() {
...@@ -322,7 +290,6 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, Drawe ...@@ -322,7 +290,6 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, Drawe
} }
fab = findViewById(R.id.fab) fab = findViewById(R.id.fab)
fabProgressCircle = findViewById(R.id.fabProgressCircle)
fab.setOnClickListener { fab.setOnClickListener {
if (state == BaseService.CONNECTED) app.stopService() else thread { if (state == BaseService.CONNECTED) app.stopService() else thread {
if (BaseService.usingVpnMode) { if (BaseService.usingVpnMode) {
...@@ -335,7 +302,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, Drawe ...@@ -335,7 +302,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, Drawe
changeState(BaseService.IDLE) // reset everything to init state changeState(BaseService.IDLE) // reset everything to init state
app.handler.post { connection.connect() } app.handler.post { connection.connect() }
DataStore.registerChangeListener(this) DataStore.publicStore.registerChangeListener(this)
val intent = this.intent val intent = this.intent
if (intent != null) handleShareIntent(intent) if (intent != null) handleShareIntent(intent)
...@@ -355,7 +322,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, Drawe ...@@ -355,7 +322,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, Drawe
} }
else -> null else -> null
} }
if (TextUtils.isEmpty(sharedStr)) return if (sharedStr.isNullOrEmpty()) return
val profiles = Profile.findAll(sharedStr).toList() val profiles = Profile.findAll(sharedStr).toList()
if (profiles.isEmpty()) { if (profiles.isEmpty()) {
Snackbar.make(findViewById(R.id.snackbar), R.string.profile_invalid_input, Snackbar.LENGTH_LONG).show() Snackbar.make(findViewById(R.id.snackbar), R.string.profile_invalid_input, Snackbar.LENGTH_LONG).show()
...@@ -404,7 +371,6 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, Drawe ...@@ -404,7 +371,6 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, Drawe
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
app.remoteConfig.fetch() app.remoteConfig.fetch()
if (state !in arrayOf(BaseService.STOPPING, BaseService.CONNECTING)) hideCircle()
} }
override fun onStart() { override fun onStart() {
...@@ -434,7 +400,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, Drawe ...@@ -434,7 +400,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, Drawe
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
DataStore.unregisterChangeListener(this) DataStore.publicStore.unregisterChangeListener(this)
connection.disconnect() connection.disconnect()
BackupManager(this).dataChanged() BackupManager(this).dataChanged()
app.handler.removeCallbacksAndMessages(null) app.handler.removeCallbacksAndMessages(null)
......
...@@ -33,7 +33,6 @@ import android.support.v7.preference.PreferenceDataStore ...@@ -33,7 +33,6 @@ import android.support.v7.preference.PreferenceDataStore
import android.support.v7.widget.Toolbar import android.support.v7.widget.Toolbar
import android.view.MenuItem import android.view.MenuItem
import com.github.shadowsocks.App.Companion.app import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.bg.BaseService
import com.github.shadowsocks.database.Profile import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.database.ProfileManager import com.github.shadowsocks.database.ProfileManager
import com.github.shadowsocks.plugin.PluginConfiguration import com.github.shadowsocks.plugin.PluginConfiguration
...@@ -45,6 +44,7 @@ import com.github.shadowsocks.preference.IconListPreference ...@@ -45,6 +44,7 @@ import com.github.shadowsocks.preference.IconListPreference
import com.github.shadowsocks.preference.OnPreferenceDataStoreChangeListener import com.github.shadowsocks.preference.OnPreferenceDataStoreChangeListener
import com.github.shadowsocks.preference.PluginConfigurationDialogFragment import com.github.shadowsocks.preference.PluginConfigurationDialogFragment
import com.github.shadowsocks.utils.Action import com.github.shadowsocks.utils.Action
import com.github.shadowsocks.utils.DirectBoot
import com.github.shadowsocks.utils.Key import com.github.shadowsocks.utils.Key
import com.takisoft.fix.support.v7.preference.EditTextPreference import com.takisoft.fix.support.v7.preference.EditTextPreference
import com.takisoft.fix.support.v7.preference.PreferenceFragmentCompatDividers import com.takisoft.fix.support.v7.preference.PreferenceFragmentCompatDividers
...@@ -62,7 +62,7 @@ class ProfileConfigFragment : PreferenceFragmentCompatDividers(), Toolbar.OnMenu ...@@ -62,7 +62,7 @@ class ProfileConfigFragment : PreferenceFragmentCompatDividers(), Toolbar.OnMenu
private lateinit var pluginConfiguration: PluginConfiguration private lateinit var pluginConfiguration: PluginConfiguration
override fun onCreatePreferencesFix(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferencesFix(savedInstanceState: Bundle?, rootKey: String?) {
preferenceManager.preferenceDataStore = DataStore preferenceManager.preferenceDataStore = DataStore.privateStore
val activity = activity!! val activity = activity!!
val profile = ProfileManager.getProfile(activity.intent.getIntExtra(Action.EXTRA_PROFILE_ID, -1)) val profile = ProfileManager.getProfile(activity.intent.getIntExtra(Action.EXTRA_PROFILE_ID, -1))
if (profile == null) { if (profile == null) {
...@@ -77,15 +77,16 @@ class ProfileConfigFragment : PreferenceFragmentCompatDividers(), Toolbar.OnMenu ...@@ -77,15 +77,16 @@ class ProfileConfigFragment : PreferenceFragmentCompatDividers(), Toolbar.OnMenu
findPreference(Key.remotePort).summary = "1337" findPreference(Key.remotePort).summary = "1337"
findPreference(Key.password).summary = "\u2022".repeat(32) findPreference(Key.password).summary = "\u2022".repeat(32)
} }
val serviceMode = DataStore.serviceMode
findPreference(Key.remoteDns).isEnabled = serviceMode != Key.modeProxy
isProxyApps = findPreference(Key.proxyApps) as SwitchPreference isProxyApps = findPreference(Key.proxyApps) as SwitchPreference
if (Build.VERSION.SDK_INT < 21) isProxyApps.parent!!.removePreference(isProxyApps) else { isProxyApps.isEnabled = serviceMode == Key.modeVpn
isProxyApps.isEnabled = BaseService.usingVpnMode
isProxyApps.setOnPreferenceClickListener { isProxyApps.setOnPreferenceClickListener {
startActivity(Intent(activity, AppManager::class.java)) startActivity(Intent(activity, AppManager::class.java))
isProxyApps.isChecked = true isProxyApps.isChecked = true
false false
} }
} findPreference(Key.udpdns).isEnabled = serviceMode != Key.modeProxy
plugin = findPreference(Key.plugin) as IconListPreference plugin = findPreference(Key.plugin) as IconListPreference
pluginConfigure = findPreference(Key.pluginConfigure) as EditTextPreference pluginConfigure = findPreference(Key.pluginConfigure) as EditTextPreference
plugin.unknownValueSummary = getString(R.string.plugin_unknown) plugin.unknownValueSummary = getString(R.string.plugin_unknown)
...@@ -102,7 +103,7 @@ class ProfileConfigFragment : PreferenceFragmentCompatDividers(), Toolbar.OnMenu ...@@ -102,7 +103,7 @@ class ProfileConfigFragment : PreferenceFragmentCompatDividers(), Toolbar.OnMenu
pluginConfigure.onPreferenceChangeListener = this pluginConfigure.onPreferenceChangeListener = this
initPlugins() initPlugins()
app.listenForPackageChanges { initPlugins() } app.listenForPackageChanges { initPlugins() }
DataStore.registerChangeListener(this) DataStore.privateStore.registerChangeListener(this)
} }
private fun initPlugins() { private fun initPlugins() {
...@@ -130,6 +131,7 @@ class ProfileConfigFragment : PreferenceFragmentCompatDividers(), Toolbar.OnMenu ...@@ -130,6 +131,7 @@ class ProfileConfigFragment : PreferenceFragmentCompatDividers(), Toolbar.OnMenu
profile.deserialize() profile.deserialize()
ProfileManager.updateProfile(profile) ProfileManager.updateProfile(profile)
ProfilesFragment.instance?.profilesAdapter?.deepRefreshId(profile.id) ProfilesFragment.instance?.profilesAdapter?.deepRefreshId(profile.id)
if (DataStore.profileId == profile.id && DataStore.directBootAware) DirectBoot.update()
activity!!.finish() activity!!.finish()
} }
...@@ -141,7 +143,7 @@ class ProfileConfigFragment : PreferenceFragmentCompatDividers(), Toolbar.OnMenu ...@@ -141,7 +143,7 @@ class ProfileConfigFragment : PreferenceFragmentCompatDividers(), Toolbar.OnMenu
override fun onPreferenceChange(preference: Preference?, newValue: Any?): Boolean = try { override fun onPreferenceChange(preference: Preference?, newValue: Any?): Boolean = try {
val selected = pluginConfiguration.selected val selected = pluginConfiguration.selected
pluginConfiguration = PluginConfiguration(pluginConfiguration.pluginsOptions + pluginConfiguration = PluginConfiguration(pluginConfiguration.pluginsOptions +
(pluginConfiguration.selected to PluginOptions(selected, newValue as String?)), selected) (pluginConfiguration.selected to PluginOptions(selected, newValue as? String?)), selected)
DataStore.plugin = pluginConfiguration.toString() DataStore.plugin = pluginConfiguration.toString()
DataStore.dirty = true DataStore.dirty = true
true true
...@@ -201,7 +203,7 @@ class ProfileConfigFragment : PreferenceFragmentCompatDividers(), Toolbar.OnMenu ...@@ -201,7 +203,7 @@ class ProfileConfigFragment : PreferenceFragmentCompatDividers(), Toolbar.OnMenu
} }
override fun onDestroy() { override fun onDestroy() {
DataStore.unregisterChangeListener(this) DataStore.privateStore.unregisterChangeListener(this)
super.onDestroy() super.onDestroy()
} }
} }
...@@ -76,7 +76,8 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -76,7 +76,8 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
} }
@SuppressLint("ValidFragment") @SuppressLint("ValidFragment")
private class QRCodeDialog() : DialogFragment() { class QRCodeDialog() : DialogFragment() {
constructor(url: String) : this() { constructor(url: String) : this() {
val bundle = Bundle() val bundle = Bundle()
bundle.putString(KEY_URL, url) bundle.putString(KEY_URL, url)
...@@ -104,9 +105,11 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -104,9 +105,11 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
} }
override fun onDetach() { override fun onDetach() {
super.onDetach()
val activity = activity
if (activity != null && !activity.isFinishing && !activity.isDestroyed)
adapter?.setNdefPushMessage(null, activity) adapter?.setNdefPushMessage(null, activity)
adapter = null adapter = null
super.onDetach()
} }
} }
...@@ -174,10 +177,13 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -174,10 +177,13 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
val params = val params =
LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT) LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
params.gravity = Gravity.CENTER_HORIZONTAL params.gravity = Gravity.CENTER_HORIZONTAL
adView = AdView(activity) val context = context!!
adView = AdView(context)
adView.layoutParams = params adView.layoutParams = params
adView.adUnitId = "ca-app-pub-9097031975646651/7760346322" adView.adUnitId = "ca-app-pub-9097031975646651/7760346322"
adView.adSize = AdSize.FLUID adView.adSize = AdSize.FLUID
val padding = context.resources.getDimensionPixelOffset(R.dimen.profile_padding)
adView.setPadding(padding, 0, 0, padding)
itemView.findViewById<LinearLayout>(R.id.content).addView(adView) itemView.findViewById<LinearLayout>(R.id.content).addView(adView)
...@@ -217,6 +223,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -217,6 +223,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
inner class ProfilesAdapter : RecyclerView.Adapter<ProfileViewHolder>() { inner class ProfilesAdapter : RecyclerView.Adapter<ProfileViewHolder>() {
internal val profiles = ProfileManager.getAllProfiles()?.toMutableList() ?: mutableListOf() internal val profiles = ProfileManager.getAllProfiles()?.toMutableList() ?: mutableListOf()
private val updated = HashSet<Profile>()
init { init {
setHasStableIds(true) // see: http://stackoverflow.com/a/32488059/2245107 setHasStableIds(true) // see: http://stackoverflow.com/a/32488059/2245107
...@@ -246,13 +253,17 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -246,13 +253,17 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
next.userOrder = previousOrder next.userOrder = previousOrder
previousOrder = order previousOrder = order
profiles[i] = next profiles[i] = next
ProfileManager.updateProfile(next) updated.add(next)
} }
first.userOrder = previousOrder first.userOrder = previousOrder
profiles[to] = first profiles[to] = first
ProfileManager.updateProfile(first) updated.add(first)
notifyItemMoved(from, to) notifyItemMoved(from, to)
} }
fun commitMove() {
updated.forEach { ProfileManager.updateProfile(it) }
updated.clear()
}
fun remove(pos: Int) { fun remove(pos: Int) {
profiles.removeAt(pos) profiles.removeAt(pos)
...@@ -341,6 +352,10 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -341,6 +352,10 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
profilesAdapter.move(viewHolder.adapterPosition, target.adapterPosition) profilesAdapter.move(viewHolder.adapterPosition, target.adapterPosition)
return true return true
} }
override fun clearView(recyclerView: RecyclerView?, viewHolder: RecyclerView.ViewHolder?) {
super.clearView(recyclerView, viewHolder)
profilesAdapter.commitMove()
}
}).attachToRecyclerView(profilesList) }).attachToRecyclerView(profilesList)
} }
......
...@@ -25,7 +25,6 @@ import android.content.Intent ...@@ -25,7 +25,6 @@ import android.content.Intent
import android.content.pm.ShortcutManager import android.content.pm.ShortcutManager
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.PersistableBundle
import android.support.v4.content.pm.ShortcutInfoCompat import android.support.v4.content.pm.ShortcutInfoCompat
import android.support.v4.content.pm.ShortcutManagerCompat import android.support.v4.content.pm.ShortcutManagerCompat
import android.support.v4.graphics.drawable.IconCompat import android.support.v4.graphics.drawable.IconCompat
...@@ -34,8 +33,8 @@ import com.github.shadowsocks.aidl.IShadowsocksService ...@@ -34,8 +33,8 @@ import com.github.shadowsocks.aidl.IShadowsocksService
import com.github.shadowsocks.bg.BaseService import com.github.shadowsocks.bg.BaseService
class QuickToggleShortcut : Activity(), ShadowsocksConnection.Interface { class QuickToggleShortcut : Activity(), ShadowsocksConnection.Interface {
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState, persistentState) super.onCreate(savedInstanceState)
if (intent.action == Intent.ACTION_CREATE_SHORTCUT) { if (intent.action == Intent.ACTION_CREATE_SHORTCUT) {
setResult(Activity.RESULT_OK, ShortcutManagerCompat.createShortcutResultIntent(this, setResult(Activity.RESULT_OK, ShortcutManagerCompat.createShortcutResultIntent(this,
ShortcutInfoCompat.Builder(this, "toggle") ShortcutInfoCompat.Builder(this, "toggle")
......
...@@ -32,6 +32,7 @@ import android.support.v7.widget.Toolbar ...@@ -32,6 +32,7 @@ import android.support.v7.widget.Toolbar
import android.widget.Toast import android.widget.Toast
import com.github.shadowsocks.database.Profile import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.database.ProfileManager import com.github.shadowsocks.database.ProfileManager
import com.github.shadowsocks.utils.resolveResourceId
import com.google.zxing.Result import com.google.zxing.Result
import me.dm7.barcodescanner.zxing.ZXingScannerView import me.dm7.barcodescanner.zxing.ZXingScannerView
...@@ -54,7 +55,7 @@ class ScannerActivity : AppCompatActivity(), ZXingScannerView.ResultHandler { ...@@ -54,7 +55,7 @@ class ScannerActivity : AppCompatActivity(), ZXingScannerView.ResultHandler {
setContentView(R.layout.layout_scanner) setContentView(R.layout.layout_scanner)
val toolbar = findViewById<Toolbar>(R.id.toolbar) val toolbar = findViewById<Toolbar>(R.id.toolbar)
toolbar.title = title toolbar.title = title
toolbar.setNavigationIcon(R.drawable.abc_ic_ab_back_material) toolbar.setNavigationIcon(theme.resolveResourceId(R.attr.homeAsUpIndicator))
toolbar.setNavigationOnClickListener { navigateUp() } toolbar.setNavigationOnClickListener { navigateUp() }
scannerView = findViewById(R.id.scanner) scannerView = findViewById(R.id.scanner)
if (Build.VERSION.SDK_INT >= 25) getSystemService(ShortcutManager::class.java).reportShortcutUsed("scan") if (Build.VERSION.SDK_INT >= 25) getSystemService(ShortcutManager::class.java).reportShortcutUsed("scan")
......
...@@ -41,6 +41,7 @@ class ShadowsocksConnection(private val instance: Interface) : ServiceConnection ...@@ -41,6 +41,7 @@ class ShadowsocksConnection(private val instance: Interface) : ServiceConnection
val serviceCallback: IShadowsocksServiceCallback? get() = null val serviceCallback: IShadowsocksServiceCallback? get() = null
val connection: ShadowsocksConnection val connection: ShadowsocksConnection
get() = connections.getOrPut(this, { ShadowsocksConnection(this) }) get() = connections.getOrPut(this, { ShadowsocksConnection(this) })
val listenForDeath get() = false
fun onServiceConnected(service: IShadowsocksService) { } fun onServiceConnected(service: IShadowsocksService) { }
/** /**
...@@ -66,7 +67,7 @@ class ShadowsocksConnection(private val instance: Interface) : ServiceConnection ...@@ -66,7 +67,7 @@ class ShadowsocksConnection(private val instance: Interface) : ServiceConnection
override fun onServiceConnected(name: ComponentName?, binder: IBinder) { override fun onServiceConnected(name: ComponentName?, binder: IBinder) {
this.binder = binder this.binder = binder
binder.linkToDeath(instance, 0) if (instance.listenForDeath) binder.linkToDeath(instance, 0)
val service = IShadowsocksService.Stub.asInterface(binder)!! val service = IShadowsocksService.Stub.asInterface(binder)!!
this.service = service this.service = service
if (instance.serviceCallback != null && !callbackRegistered) try { if (instance.serviceCallback != null && !callbackRegistered) try {
...@@ -106,7 +107,7 @@ class ShadowsocksConnection(private val instance: Interface) : ServiceConnection ...@@ -106,7 +107,7 @@ class ShadowsocksConnection(private val instance: Interface) : ServiceConnection
(instance as Context).unbindService(this) (instance as Context).unbindService(this)
} catch (_: IllegalArgumentException) { } // ignore } catch (_: IllegalArgumentException) { } // ignore
connectionActive = false connectionActive = false
binder?.unlinkToDeath(instance, 0) if (instance.listenForDeath) binder?.unlinkToDeath(instance, 0)
binder = null binder = null
service = null service = null
} }
......
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
package com.github.shadowsocks package com.github.shadowsocks
import android.app.Activity
import android.app.KeyguardManager import android.app.KeyguardManager
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
...@@ -27,15 +28,13 @@ import android.content.Intent ...@@ -27,15 +28,13 @@ import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.net.VpnService import android.net.VpnService
import android.os.Bundle import android.os.Bundle
import android.os.PersistableBundle
import android.support.v7.app.AppCompatActivity
import android.util.Log import android.util.Log
import com.github.shadowsocks.App.Companion.app import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.aidl.IShadowsocksService import com.github.shadowsocks.aidl.IShadowsocksService
import com.github.shadowsocks.bg.BaseService import com.github.shadowsocks.bg.BaseService
import com.github.shadowsocks.utils.broadcastReceiver import com.github.shadowsocks.utils.broadcastReceiver
class VpnRequestActivity : AppCompatActivity(), ShadowsocksConnection.Interface { class VpnRequestActivity : Activity(), ShadowsocksConnection.Interface {
companion object { companion object {
private const val TAG = "VpnRequestActivity" private const val TAG = "VpnRequestActivity"
private const val REQUEST_CONNECT = 1 private const val REQUEST_CONNECT = 1
...@@ -43,8 +42,8 @@ class VpnRequestActivity : AppCompatActivity(), ShadowsocksConnection.Interface ...@@ -43,8 +42,8 @@ class VpnRequestActivity : AppCompatActivity(), ShadowsocksConnection.Interface
private var receiver: BroadcastReceiver? = null private var receiver: BroadcastReceiver? = null
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState, persistentState) super.onCreate(savedInstanceState)
if (!BaseService.usingVpnMode) { if (!BaseService.usingVpnMode) {
finish() finish()
return return
......
...@@ -20,14 +20,15 @@ ...@@ -20,14 +20,15 @@
package com.github.shadowsocks.acl package com.github.shadowsocks.acl
import android.content.Context
import android.support.v7.util.SortedList import android.support.v7.util.SortedList
import android.util.Log import android.util.Log
import com.github.shadowsocks.App.Companion.app import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.Subnet import com.github.shadowsocks.utils.Subnet
import com.github.shadowsocks.utils.asIterable import com.github.shadowsocks.utils.asIterable
import com.j256.ormlite.field.DatabaseField
import java.io.File import java.io.File
import java.io.FileNotFoundException import java.io.IOException
import java.io.Reader import java.io.Reader
import java.net.URL import java.net.URL
...@@ -41,21 +42,29 @@ class Acl { ...@@ -41,21 +42,29 @@ class Acl {
const val GFWLIST = "gfwlist" const val GFWLIST = "gfwlist"
const val CHINALIST = "china-list" const val CHINALIST = "china-list"
const val CUSTOM_RULES = "custom-rules" const val CUSTOM_RULES = "custom-rules"
const val CUSTOM_RULES_FLATTENED = "custom-rules-flattened"
val networkAclParser = "^IMPORT_URL\\s*<(.+)>\\s*$".toRegex() val networkAclParser = "^IMPORT_URL\\s*<(.+)>\\s*$".toRegex()
fun getFile(id: String) = File(app.filesDir, id + ".acl") fun getFile(id: String, context: Context = app.deviceContext) = File(context.filesDir, id + ".acl")
val customRules: Acl get() {
var customRules: Acl
get() {
val acl = Acl() val acl = Acl()
try { val str = DataStore.publicStore.getString(CUSTOM_RULES)
acl.fromId(CUSTOM_RULES) if (str != null) {
} catch (_: FileNotFoundException) { } acl.fromReader(str.reader())
if (!acl.bypass) {
acl.subnets.clear()
acl.hostnames.clear()
acl.bypass = true acl.bypass = true
acl.bypassHostnames.clear() // everything is bypassed }
} else acl.bypass = true
return acl return acl
} }
fun save(id: String, acl: Acl): Unit = getFile(id).bufferedWriter().use { it.write(acl.toString()) } set(value) = DataStore.publicStore.putString(CUSTOM_RULES, if ((!value.bypass ||
value.subnets.size() == 0 && value.hostnames.size() == 0) && value.urls.size() == 0)
null else value.toString())
fun save(id: String, acl: Acl) = getFile(id).writeText(acl.toString())
} }
private abstract class BaseSorter<T> : SortedList.Callback<T>() { private abstract class BaseSorter<T> : SortedList.Callback<T>() {
...@@ -79,41 +88,39 @@ class Acl { ...@@ -79,41 +88,39 @@ class Acl {
override fun compareNonNull(o1: URL, o2: URL): Int = ordering.compare(o1, o2) override fun compareNonNull(o1: URL, o2: URL): Int = ordering.compare(o1, o2)
} }
@DatabaseField(generatedId = true) val hostnames = SortedList(String::class.java, StringSorter)
var id = 0
val bypassHostnames = SortedList(String::class.java, StringSorter)
val proxyHostnames = SortedList(String::class.java, StringSorter)
val subnets = SortedList(Subnet::class.java, SubnetSorter) val subnets = SortedList(Subnet::class.java, SubnetSorter)
val urls = SortedList(URL::class.java, URLSorter) val urls = SortedList(URL::class.java, URLSorter)
@DatabaseField
var bypass = false var bypass = false
fun fromAcl(other: Acl): Acl { fun clear(): Acl {
bypassHostnames.clear() hostnames.clear()
for (item in other.bypassHostnames.asIterable()) bypassHostnames.add(item)
proxyHostnames.clear()
for (item in other.proxyHostnames.asIterable()) proxyHostnames.add(item)
subnets.clear() subnets.clear()
for (item in other.subnets.asIterable()) subnets.add(item)
urls.clear() urls.clear()
return this
}
fun fromAcl(other: Acl): Acl {
clear()
for (item in other.hostnames.asIterable()) hostnames.add(item)
for (item in other.subnets.asIterable()) subnets.add(item)
for (item in other.urls.asIterable()) urls.add(item) for (item in other.urls.asIterable()) urls.add(item)
bypass = other.bypass bypass = other.bypass
return this return this
} }
fun fromReader(reader: Reader, defaultBypass: Boolean = false): Acl { fun fromReader(reader: Reader, defaultBypass: Boolean = false): Acl {
bypassHostnames.clear() clear()
proxyHostnames.clear()
subnets.clear()
urls.clear()
bypass = defaultBypass bypass = defaultBypass
val proxyHostnames by lazy { SortedList(String::class.java, StringSorter) }
val bypassHostnames by lazy { SortedList(String::class.java, StringSorter) }
val bypassSubnets by lazy { SortedList(Subnet::class.java, SubnetSorter) } val bypassSubnets by lazy { SortedList(Subnet::class.java, SubnetSorter) }
val proxySubnets by lazy { SortedList(Subnet::class.java, SubnetSorter) } val proxySubnets by lazy { SortedList(Subnet::class.java, SubnetSorter) }
var hostnames: SortedList<String>? = if (defaultBypass) proxyHostnames else bypassHostnames var hostnames: SortedList<String>? = if (defaultBypass) proxyHostnames else bypassHostnames
var subnets: SortedList<Subnet>? = if (defaultBypass) proxySubnets else bypassSubnets var subnets: SortedList<Subnet>? = if (defaultBypass) proxySubnets else bypassSubnets
reader.useLines { reader.useLines {
for (line in it) { for (line in it) {
val blocks = line.split('#', limit = 2) @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
val blocks = (line as java.lang.String).split("#", 2)
val url = networkAclParser.matchEntire(blocks.getOrElse(1, {""}))?.groupValues?.getOrNull(1) val url = networkAclParser.matchEntire(blocks.getOrElse(1, {""}))?.groupValues?.getOrNull(1)
if (url != null) urls.add(URL(url)) if (url != null) urls.add(URL(url))
val input = blocks[0].trim() val input = blocks[0].trim()
...@@ -132,30 +139,39 @@ class Acl { ...@@ -132,30 +139,39 @@ class Acl {
} }
"[reject_all]", "[bypass_all]" -> bypass = true "[reject_all]", "[bypass_all]" -> bypass = true
"[accept_all]", "[proxy_all]" -> bypass = false "[accept_all]", "[proxy_all]" -> bypass = false
else -> if (subnets != null && input.isNotEmpty()) try { else -> if (subnets != null && input.isNotEmpty()) {
subnets!!.add(Subnet.fromString(input)) val subnet = Subnet.fromString(input)
} catch (_: IllegalArgumentException) { if (subnet == null) hostnames!!.add(input) else subnets!!.add(subnet)
hostnames!!.add(input)
} }
} }
} }
} }
for (item in (if (bypass) proxyHostnames else bypassHostnames).asIterable()) this.hostnames.add(item)
for (item in (if (bypass) proxySubnets else bypassSubnets).asIterable()) this.subnets.add(item) for (item in (if (bypass) proxySubnets else bypassSubnets).asIterable()) this.subnets.add(item)
return this return this
} }
fun fromId(id: String): Acl = fromReader(Acl.getFile(id).bufferedReader())
fun fromId(id: String): Acl = try {
fromReader(Acl.getFile(id).bufferedReader())
} catch (_: IOException) { this }
fun flatten(depth: Int): Acl { fun flatten(depth: Int): Acl {
if (depth > 0) for (url in urls.asIterable()) { if (depth > 0) for (url in urls.asIterable()) {
val child = Acl().fromReader(url.openStream().bufferedReader(), bypass).flatten(depth - 1) val child = Acl()
try {
child.fromReader(url.openStream().bufferedReader(), bypass).flatten(depth - 1)
} catch (_: IOException) {
continue
}
if (bypass != child.bypass) { if (bypass != child.bypass) {
Log.w(TAG, "Imported network ACL has a conflicting mode set. " + Log.w(TAG, "Imported network ACL has a conflicting mode set. " +
"This will probably not work as intended. URL: $url") "This will probably not work as intended. URL: $url")
child.subnets.clear() // subnets for the different mode are discarded // rules for the different mode are discarded
child.hostnames.clear()
child.subnets.clear()
child.bypass = bypass child.bypass = bypass
} }
for (item in child.bypassHostnames.asIterable()) bypassHostnames.add(item) for (item in child.hostnames.asIterable()) hostnames.add(item)
for (item in child.proxyHostnames.asIterable()) proxyHostnames.add(item)
for (item in child.subnets.asIterable()) subnets.add(item) for (item in child.subnets.asIterable()) subnets.add(item)
} }
urls.clear() urls.clear()
...@@ -164,21 +180,11 @@ class Acl { ...@@ -164,21 +180,11 @@ class Acl {
override fun toString(): String { override fun toString(): String {
val result = StringBuilder() val result = StringBuilder()
result.append(if (bypass) "[bypass_all]\n" else "[proxy_all]\n") result.append(if (bypass) "[bypass_all]\n[proxy_list]\n" else "[proxy_all]\n[bypass_list]\n")
val bypassList = (if (bypass) bypassHostnames.asIterable().asSequence() else result.append(subnets.asIterable().joinToString("\n"))
subnets.asIterable().asSequence().map(Subnet::toString) + proxyHostnames.asIterable().asSequence()).toList()
val proxyList = (if (bypass) subnets.asIterable().asSequence().map(Subnet::toString) +
proxyHostnames.asIterable().asSequence() else bypassHostnames.asIterable().asSequence()).toList()
if (bypassList.isNotEmpty()) {
result.append("[bypass_list]\n")
result.append(bypassList.joinToString("\n"))
result.append('\n') result.append('\n')
} result.append(hostnames.asIterable().joinToString("\n"))
if (proxyList.isNotEmpty()) {
result.append("[proxy_list]\n")
result.append(proxyList.joinToString("\n"))
result.append('\n') result.append('\n')
}
result.append(urls.asIterable().joinToString("") { "#IMPORT_URL <$it>\n" }) result.append(urls.asIterable().joinToString("") { "#IMPORT_URL <$it>\n" })
return result.toString() return result.toString()
} }
......
...@@ -48,7 +48,6 @@ import com.github.shadowsocks.ToolbarFragment ...@@ -48,7 +48,6 @@ import com.github.shadowsocks.ToolbarFragment
import com.github.shadowsocks.bg.BaseService import com.github.shadowsocks.bg.BaseService
import com.github.shadowsocks.utils.Subnet import com.github.shadowsocks.utils.Subnet
import com.github.shadowsocks.utils.asIterable import com.github.shadowsocks.utils.asIterable
import com.github.shadowsocks.utils.dp
import com.github.shadowsocks.widget.UndoSnackbarManager import com.github.shadowsocks.widget.UndoSnackbarManager
import java.net.IDN import java.net.IDN
import java.net.URL import java.net.URL
...@@ -62,7 +61,8 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -62,7 +61,8 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
private const val SELECTED_HOSTNAMES = "com.github.shadowsocks.acl.CustomRulesFragment.SELECTED_HOSTNAMES" private const val SELECTED_HOSTNAMES = "com.github.shadowsocks.acl.CustomRulesFragment.SELECTED_HOSTNAMES"
private const val SELECTED_URLS = "com.github.shadowsocks.acl.CustomRulesFragment.SELECTED_URLS" private const val SELECTED_URLS = "com.github.shadowsocks.acl.CustomRulesFragment.SELECTED_URLS"
private val PATTERN_DOMAIN = """(?<=^\(\^\|\\\.\)).*(?=\$$)""".toRegex() // unescaped: (?<=^(\(\^\|\\\.\)|\^\(\.\*\\\.\)\?)).*(?=\$$)
private val PATTERN_DOMAIN = "(?<=^(\\(\\^\\|\\\\\\.\\)|\\^\\(\\.\\*\\\\\\.\\)\\?)).*(?=\\\$\$)".toRegex()
} }
private enum class Template { private enum class Template {
...@@ -78,7 +78,8 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -78,7 +78,8 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
init { init {
view.setPaddingRelative(view.paddingStart, view.paddingTop, view.setPaddingRelative(view.paddingStart, view.paddingTop,
Math.max(view.paddingEnd, 20f.dp().toInt()), view.paddingBottom) Math.max(view.paddingEnd, resources.getDimensionPixelSize(R.dimen.fastscroll__bubble_corner)),
view.paddingBottom)
view.setOnClickListener(this) view.setOnClickListener(this)
view.setOnLongClickListener(this) view.setOnLongClickListener(this)
view.setBackgroundResource(R.drawable.background_selectable) view.setBackgroundResource(R.drawable.background_selectable)
...@@ -132,20 +133,20 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -132,20 +133,20 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
override fun onBindViewHolder(holder: AclRuleViewHolder, i: Int) { override fun onBindViewHolder(holder: AclRuleViewHolder, i: Int) {
val j = i - acl.subnets.size() val j = i - acl.subnets.size()
if (j < 0) holder.bind(acl.subnets[i]) else { if (j < 0) holder.bind(acl.subnets[i]) else {
val k = j - acl.proxyHostnames.size() val k = j - acl.hostnames.size()
if (k < 0) holder.bind(acl.proxyHostnames[j]) else holder.bind(acl.urls[k]) if (k < 0) holder.bind(acl.hostnames[j]) else holder.bind(acl.urls[k])
} }
} }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = AclRuleViewHolder(LayoutInflater override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = AclRuleViewHolder(LayoutInflater
.from(parent.context).inflate(android.R.layout.simple_list_item_1, parent, false)) .from(parent.context).inflate(android.R.layout.simple_list_item_1, parent, false))
override fun getItemCount(): Int = acl.subnets.size() + acl.proxyHostnames.size() + acl.urls.size() override fun getItemCount(): Int = acl.subnets.size() + acl.hostnames.size() + acl.urls.size()
override fun getSectionTitle(i: Int): String { override fun getSectionTitle(i: Int): String {
val j = i - acl.subnets.size() val j = i - acl.subnets.size()
return try { return try {
(if (j < 0) acl.subnets[i].address.hostAddress.substring(0, 1) else { (if (j < 0) acl.subnets[i].address.hostAddress.substring(0, 1) else {
val k = j - acl.proxyHostnames.size() val k = j - acl.hostnames.size()
if (k < 0) { if (k < 0) {
val hostname = acl.proxyHostnames[j] val hostname = acl.hostnames[j]
// don't convert IDN yet // don't convert IDN yet
PATTERN_DOMAIN.find(hostname)?.value?.replace("\\.", ".") ?: hostname PATTERN_DOMAIN.find(hostname)?.value?.replace("\\.", ".") ?: hostname
} else acl.urls[k].host } else acl.urls[k].host
...@@ -157,7 +158,7 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -157,7 +158,7 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
if (!savePending) { if (!savePending) {
savePending = true savePending = true
list.post { list.post {
Acl.save(Acl.CUSTOM_RULES, acl) Acl.customRules = acl
savePending = false savePending = false
} }
} }
...@@ -179,9 +180,9 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -179,9 +180,9 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
return index return index
} }
fun addHostname(hostname: String): Int { fun addHostname(hostname: String): Int {
val old = acl.proxyHostnames.size() val old = acl.hostnames.size()
val index = acl.subnets.size() + acl.proxyHostnames.add(hostname) val index = acl.subnets.size() + acl.hostnames.add(hostname)
if (old != acl.proxyHostnames.size()) { if (old != acl.hostnames.size()) {
notifyItemInserted(index) notifyItemInserted(index)
apply() apply()
} }
...@@ -189,7 +190,7 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -189,7 +190,7 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
} }
fun addURL(url: URL): Int { fun addURL(url: URL): Int {
val old = acl.urls.size() val old = acl.urls.size()
val index = acl.subnets.size() + acl.proxyHostnames.size() + acl.urls.add(url) val index = acl.subnets.size() + acl.hostnames.size() + acl.urls.add(url)
if (old != acl.urls.size()) { if (old != acl.urls.size()) {
notifyItemInserted(index) notifyItemInserted(index)
apply() apply()
...@@ -201,7 +202,7 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -201,7 +202,7 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
var result: Int? = null var result: Int? = null
if (acl.bypass) acl.subnets.asIterable().asSequence().map { addSubnet(it) } if (acl.bypass) acl.subnets.asIterable().asSequence().map { addSubnet(it) }
.forEach { if (result == null) result = it } .forEach { if (result == null) result = it }
(acl.proxyHostnames.asIterable().asSequence().map { addHostname(it) } + (acl.hostnames.asIterable().asSequence().map { addHostname(it) } +
acl.urls.asIterable().asSequence().map { addURL(it) }) acl.urls.asIterable().asSequence().map { addURL(it) })
.forEach { if (result == null) result = it } .forEach { if (result == null) result = it }
return result return result
...@@ -227,10 +228,10 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -227,10 +228,10 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
undoManager.remove(Pair(i, acl.subnets[i])) undoManager.remove(Pair(i, acl.subnets[i]))
acl.subnets.removeItemAt(i) acl.subnets.removeItemAt(i)
} else { } else {
val k = j - acl.proxyHostnames.size() val k = j - acl.hostnames.size()
if (k < 0) { if (k < 0) {
undoManager.remove(Pair(j, acl.proxyHostnames[j])) undoManager.remove(Pair(j, acl.hostnames[j]))
acl.proxyHostnames.removeItemAt(j) acl.hostnames.removeItemAt(j)
} else { } else {
undoManager.remove(Pair(k, acl.urls[k])) undoManager.remove(Pair(k, acl.urls[k]))
acl.urls.removeItemAt(k) acl.urls.removeItemAt(k)
...@@ -247,12 +248,12 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -247,12 +248,12 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
apply() apply()
} }
is String -> { is String -> {
notifyItemRemoved(acl.subnets.size() + acl.proxyHostnames.indexOf(item)) notifyItemRemoved(acl.subnets.size() + acl.hostnames.indexOf(item))
acl.proxyHostnames.remove(item) acl.hostnames.remove(item)
apply() apply()
} }
is URL -> { is URL -> {
notifyItemRemoved(acl.subnets.size() + acl.proxyHostnames.size() + acl.urls.indexOf(item)) notifyItemRemoved(acl.subnets.size() + acl.hostnames.size() + acl.urls.indexOf(item))
acl.urls.remove(item) acl.urls.remove(item)
apply() apply()
} }
...@@ -271,7 +272,7 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -271,7 +272,7 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
fun selectAll() { fun selectAll() {
selectedItems.clear() selectedItems.clear()
selectedItems.addAll(acl.subnets.asIterable()) selectedItems.addAll(acl.subnets.asIterable())
selectedItems.addAll(acl.proxyHostnames.asIterable()) selectedItems.addAll(acl.hostnames.asIterable())
selectedItems.addAll(acl.urls.asIterable()) selectedItems.addAll(acl.urls.asIterable())
onSelectedItemsUpdated() onSelectedItemsUpdated()
notifyDataSetChanged() notifyDataSetChanged()
...@@ -333,8 +334,8 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -333,8 +334,8 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
if (savedInstanceState != null) { if (savedInstanceState != null) {
selectedItems.addAll(savedInstanceState.getStringArray(SELECTED_SUBNETS)?.map(Subnet.Companion::fromString) selectedItems.addAll(savedInstanceState.getStringArray(SELECTED_SUBNETS)
?: listOf()) ?.mapNotNull(Subnet.Companion::fromString) ?: listOf())
selectedItems.addAll(savedInstanceState.getStringArray(SELECTED_HOSTNAMES) selectedItems.addAll(savedInstanceState.getStringArray(SELECTED_HOSTNAMES)
?: arrayOf()) ?: arrayOf())
selectedItems.addAll(savedInstanceState.getStringArray(SELECTED_URLS)?.map { URL(it) } selectedItems.addAll(savedInstanceState.getStringArray(SELECTED_URLS)?.map { URL(it) }
...@@ -355,7 +356,7 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -355,7 +356,7 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
undoManager = UndoSnackbarManager(activity!!.findViewById(R.id.snackbar), adapter::undo) undoManager = UndoSnackbarManager(activity!!.findViewById(R.id.snackbar), adapter::undo)
ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.START or ItemTouchHelper.END) { ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.START or ItemTouchHelper.END) {
override fun getSwipeDirs(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int = override fun getSwipeDirs(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int =
if (isEnabled) super.getSwipeDirs(recyclerView, viewHolder) else 0 if (isEnabled && selectedItems.isEmpty()) super.getSwipeDirs(recyclerView, viewHolder) else 0
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) = override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) =
adapter.remove(viewHolder.adapterPosition) adapter.remove(viewHolder.adapterPosition)
override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder,
...@@ -386,7 +387,7 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -386,7 +387,7 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
selectedItems.forEach { selectedItems.forEach {
when (it) { when (it) {
is Subnet -> acl.subnets.add(it) is Subnet -> acl.subnets.add(it)
is String -> acl.proxyHostnames.add(it) is String -> acl.hostnames.add(it)
is URL -> acl.urls.add(it) is URL -> acl.urls.add(it)
} }
} }
...@@ -432,10 +433,15 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener { ...@@ -432,10 +433,15 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
R.id.action_import_gfwlist -> { R.id.action_import_gfwlist -> {
val acl = Acl().fromId(Acl.GFWLIST) val acl = Acl().fromId(Acl.GFWLIST)
if (!acl.bypass) acl.subnets.asIterable().forEach { adapter.addSubnet(it) } if (!acl.bypass) acl.subnets.asIterable().forEach { adapter.addSubnet(it) }
acl.proxyHostnames.asIterable().forEach { adapter.addHostname(it) } acl.hostnames.asIterable().forEach { adapter.addHostname(it) }
acl.urls.asIterable().forEach { adapter.addURL(it) } acl.urls.asIterable().forEach { adapter.addURL(it) }
true true
} }
else -> false else -> false
} }
override fun onDetach() {
undoManager.flush()
super.onDetach()
}
} }
...@@ -20,12 +20,15 @@ ...@@ -20,12 +20,15 @@
package com.github.shadowsocks.bg package com.github.shadowsocks.bg
import android.annotation.TargetApi
import android.app.Service import android.app.Service
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.os.* import android.os.Build
import android.text.TextUtils import android.os.IBinder
import android.os.RemoteCallbackList
import android.support.v4.os.UserManagerCompat
import android.util.Base64 import android.util.Base64
import android.util.Log import android.util.Log
import android.widget.Toast import android.widget.Toast
...@@ -66,6 +69,8 @@ object BaseService { ...@@ -66,6 +69,8 @@ object BaseService {
const val STOPPING = 3 const val STOPPING = 3
const val STOPPED = 4 const val STOPPED = 4
const val CONFIG_FILE = "shadowsocks.conf"
class Data internal constructor(private val service: Interface) { class Data internal constructor(private val service: Interface) {
@Volatile var profile: Profile? = null @Volatile var profile: Profile? = null
@Volatile var state = STOPPED @Volatile var state = STOPPED
...@@ -91,10 +96,8 @@ object BaseService { ...@@ -91,10 +96,8 @@ object BaseService {
} }
var closeReceiverRegistered = false var closeReceiverRegistered = false
private fun state() = state
val binder = object : IShadowsocksService.Stub() { val binder = object : IShadowsocksService.Stub() {
override fun getState(): Int = state() override fun getState(): Int = this@Data.state
override fun getProfileName(): String = profile?.formattedName ?: "Idle" override fun getProfileName(): String = profile?.formattedName ?: "Idle"
override fun registerCallback(cb: IShadowsocksServiceCallback) { override fun registerCallback(cb: IShadowsocksServiceCallback) {
...@@ -165,7 +168,8 @@ object BaseService { ...@@ -165,7 +168,8 @@ object BaseService {
} }
} }
internal fun buildShadowsocksConfig() { internal var shadowsocksConfigFile: File? = null
internal fun buildShadowsocksConfig(): File {
val profile = profile!! val profile = profile!!
val config = JSONObject() val config = JSONObject()
.put("server", profile.host) .put("server", profile.host)
...@@ -180,7 +184,30 @@ object BaseService { ...@@ -180,7 +184,30 @@ object BaseService {
.put("plugin", Commandline.toString(service.buildAdditionalArguments(pluginCmd))) .put("plugin", Commandline.toString(service.buildAdditionalArguments(pluginCmd)))
.put("plugin_opts", plugin.toString()) .put("plugin_opts", plugin.toString())
} }
File(app.filesDir, "shadowsocks.json").bufferedWriter().use { it.write(config.toString()) } // sensitive Shadowsocks config is stored in
val file = File((if (UserManagerCompat.isUserUnlocked(app)) app.filesDir else @TargetApi(24) {
app.deviceContext.noBackupFilesDir // only API 24+ will be in locked state
}), CONFIG_FILE)
shadowsocksConfigFile = file
file.writeText(config.toString())
return file
}
val aclFile: File? get() {
val route = profile!!.route
return if (route == Acl.ALL) null else Acl.getFile(route)
}
fun changeState(s: Int, msg: String? = null) {
if (state == s && msg == null) return
if (callbacks.registeredCallbackCount > 0) app.handler.post {
val n = callbacks.beginBroadcast()
for (i in 0 until n) try {
callbacks.getBroadcastItem(i).stateChanged(s, binder.profileName, msg)
} catch (_: Exception) { } // ignore
callbacks.finishBroadcast()
}
state = s
} }
} }
interface Interface { interface Interface {
...@@ -189,7 +216,7 @@ object BaseService { ...@@ -189,7 +216,7 @@ object BaseService {
fun onBind(intent: Intent): IBinder? = if (intent.action == Action.SERVICE) data.binder else null fun onBind(intent: Intent): IBinder? = if (intent.action == Action.SERVICE) data.binder else null
fun checkProfile(profile: Profile): Boolean = fun checkProfile(profile: Profile): Boolean =
if (TextUtils.isEmpty(profile.host) || TextUtils.isEmpty(profile.password)) { if (profile.host.isEmpty() || profile.password.isEmpty()) {
stopRunner(true, (this as Context).getString(R.string.proxy_empty)) stopRunner(true, (this as Context).getString(R.string.proxy_empty))
false false
} else true } else true
...@@ -212,19 +239,18 @@ object BaseService { ...@@ -212,19 +239,18 @@ object BaseService {
fun startNativeProcesses() { fun startNativeProcesses() {
val data = data val data = data
data.buildShadowsocksConfig()
val cmd = buildAdditionalArguments(arrayListOf( val cmd = buildAdditionalArguments(arrayListOf(
File((this as Context).applicationInfo.nativeLibraryDir, Executable.SS_LOCAL).absolutePath, File((this as Context).applicationInfo.nativeLibraryDir, Executable.SS_LOCAL).absolutePath,
"-u", "-u",
"-b", "127.0.0.1", "-b", "127.0.0.1",
"-l", DataStore.portProxy.toString(), "-l", DataStore.portProxy.toString(),
"-t", "600", "-t", "600",
"-c", "shadowsocks.json")) "-c", data.buildShadowsocksConfig().absolutePath))
val route = data.profile!!.route val acl = data.aclFile
if (route != Acl.ALL) { if (acl != null) {
cmd += "--acl" cmd += "--acl"
cmd += Acl.getFile(if (route == Acl.CUSTOM_RULES) Acl.CUSTOM_RULES_FLATTENED else route).absolutePath cmd += acl.absolutePath
} }
if (TcpFastOpen.sendEnabled) cmd += "--fast-open" if (TcpFastOpen.sendEnabled) cmd += "--fast-open"
...@@ -232,7 +258,7 @@ object BaseService { ...@@ -232,7 +258,7 @@ object BaseService {
data.sslocalProcess = GuardedProcess(cmd).start() data.sslocalProcess = GuardedProcess(cmd).start()
} }
fun createNotification(): ServiceNotification fun createNotification(profileName: String): ServiceNotification
fun startRunner() { fun startRunner() {
this as Context this as Context
...@@ -248,20 +274,23 @@ object BaseService { ...@@ -248,20 +274,23 @@ object BaseService {
fun stopRunner(stopService: Boolean, msg: String? = null) { fun stopRunner(stopService: Boolean, msg: String? = null) {
// channge the state // channge the state
changeState(STOPPING) val data = data
data.changeState(STOPPING)
app.track(tag, "stop") app.track(tag, "stop")
killProcesses() killProcesses()
// clean up recevier // clean up recevier
val data = data
this as Service this as Service
if (data.closeReceiverRegistered) { if (data.closeReceiverRegistered) {
unregisterReceiver(data.closeReceiver) unregisterReceiver(data.closeReceiver)
data.closeReceiverRegistered = false data.closeReceiverRegistered = false
} }
data.shadowsocksConfigFile?.delete() // remove old config possibly in device storage
data.shadowsocksConfigFile = null
data.notification?.destroy() data.notification?.destroy()
data.notification = null data.notification = null
...@@ -273,7 +302,7 @@ object BaseService { ...@@ -273,7 +302,7 @@ object BaseService {
data.trafficMonitorThread = null data.trafficMonitorThread = null
// change the state // change the state
changeState(STOPPED, msg) data.changeState(STOPPED, msg)
// stop the service if nothing has bound to it // stop the service if nothing has bound to it
if (stopService) stopSelf() if (stopService) stopSelf()
...@@ -287,12 +316,13 @@ object BaseService { ...@@ -287,12 +316,13 @@ object BaseService {
val data = data val data = data
if (data.state != STOPPED) return Service.START_NOT_STICKY if (data.state != STOPPED) return Service.START_NOT_STICKY
val profile = app.currentProfile val profile = app.currentProfile
this as Context
if (profile == null) { if (profile == null) {
stopRunner(true) data.notification = createNotification("") // gracefully shutdown: https://stackoverflow.com/questions/47337857/context-startforegroundservice-did-not-then-call-service-startforeground-eve
stopRunner(true, getString(R.string.profile_empty))
return Service.START_NOT_STICKY return Service.START_NOT_STICKY
} }
data.profile = profile data.profile = profile
profile.name = profile.formattedName
TrafficMonitor.reset() TrafficMonitor.reset()
val thread = TrafficMonitorThread() val thread = TrafficMonitorThread()
...@@ -305,17 +335,16 @@ object BaseService { ...@@ -305,17 +335,16 @@ object BaseService {
filter.addAction(Action.RELOAD) filter.addAction(Action.RELOAD)
filter.addAction(Intent.ACTION_SHUTDOWN) filter.addAction(Intent.ACTION_SHUTDOWN)
filter.addAction(Action.CLOSE) filter.addAction(Action.CLOSE)
(this as Context).registerReceiver(data.closeReceiver, filter) registerReceiver(data.closeReceiver, filter)
data.closeReceiverRegistered = true data.closeReceiverRegistered = true
} }
data.notification = createNotification() data.notification = createNotification(profile.formattedName)
app.track(tag, "start") app.track(tag, "start")
changeState(CONNECTING) data.changeState(CONNECTING)
thread { thread {
this as Context
try { try {
if (profile.host == "198.199.101.152") { if (profile.host == "198.199.101.152") {
val client = OkHttpClient.Builder() val client = OkHttpClient.Builder()
...@@ -348,7 +377,7 @@ object BaseService { ...@@ -348,7 +377,7 @@ object BaseService {
} }
if (profile.route == Acl.CUSTOM_RULES) if (profile.route == Acl.CUSTOM_RULES)
Acl.save(Acl.CUSTOM_RULES_FLATTENED, Acl.customRules.flatten(10)) Acl.save(Acl.CUSTOM_RULES, Acl.customRules.flatten(10))
data.plugin = PluginConfiguration(profile.plugin ?: "").selectedOptions data.plugin = PluginConfiguration(profile.plugin ?: "").selectedOptions
data.pluginPath = PluginManager.init(data.plugin) data.pluginPath = PluginManager.init(data.plugin)
...@@ -363,7 +392,7 @@ object BaseService { ...@@ -363,7 +392,7 @@ object BaseService {
if (profile.route !in arrayOf(Acl.ALL, Acl.CUSTOM_RULES)) AclSyncJob.schedule(profile.route) if (profile.route !in arrayOf(Acl.ALL, Acl.CUSTOM_RULES)) AclSyncJob.schedule(profile.route)
changeState(CONNECTED) data.changeState(CONNECTED)
} catch (_: UnknownHostException) { } catch (_: UnknownHostException) {
stopRunner(true, getString(R.string.invalid_server)) stopRunner(true, getString(R.string.invalid_server))
} catch (_: VpnService.NullConnectionException) { } catch (_: VpnService.NullConnectionException) {
...@@ -375,19 +404,6 @@ object BaseService { ...@@ -375,19 +404,6 @@ object BaseService {
} }
return Service.START_NOT_STICKY return Service.START_NOT_STICKY
} }
fun changeState(s: Int, msg: String? = null) {
val data = instances[this]!!
if (data.state == s && msg == null) return
if (data.callbacks.registeredCallbackCount > 0) app.handler.post {
val n = data.callbacks.beginBroadcast()
for (i in 0 until n) try {
data.callbacks.getBroadcastItem(i).stateChanged(s, data.binder.profileName, msg)
} catch (_: Exception) { } // ignore
data.callbacks.finishBroadcast()
}
data.state = s
}
} }
private val instances = WeakHashMap<Interface, Data>() private val instances = WeakHashMap<Interface, Data>()
......
...@@ -20,49 +20,19 @@ ...@@ -20,49 +20,19 @@
package com.github.shadowsocks.bg 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 android.util.Log
import com.github.shadowsocks.App.Companion.app import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.BuildConfig import com.github.shadowsocks.BuildConfig
import okhttp3.Dns as Okdns
import org.xbill.DNS.* import org.xbill.DNS.*
import java.net.Inet6Address import java.net.Inet6Address
import java.net.InetAddress import java.net.InetAddress
import java.net.NetworkInterface import java.net.NetworkInterface
import java.net.UnknownHostException import java.net.UnknownHostException
import java.util.* import okhttp3.Dns as Okdns
object Dns { object Dns {
private const val TAG = "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 { private val hasIPv6Support get() = try {
val result = NetworkInterface.getNetworkInterfaces().asSequence().flatMap { it.inetAddresses.asSequence() } val result = NetworkInterface.getNetworkInterfaces().asSequence().flatMap { it.inetAddresses.asSequence() }
.count { it is Inet6Address && !it.isLoopbackAddress && !it.isLinkLocalAddress } > 0 .count { it is Inet6Address && !it.isLoopbackAddress && !it.isLinkLocalAddress } > 0
......
...@@ -37,8 +37,7 @@ object Executable { ...@@ -37,8 +37,7 @@ object Executable {
fun killAll() { fun killAll() {
for (process in File("/proc").listFiles { _, name -> TextUtils.isDigitsOnly(name) }) { for (process in File("/proc").listFiles { _, name -> TextUtils.isDigitsOnly(name) }) {
val exe = File(File(process, "cmdline").bufferedReader().use { it.readText() } val exe = File(File(process, "cmdline").readText().split(Character.MIN_VALUE, limit = 2).first())
.split(Character.MIN_VALUE, limit = 2).first())
if (exe.parent == app.applicationInfo.nativeLibraryDir && EXECUTABLES.contains(exe.name)) { if (exe.parent == app.applicationInfo.nativeLibraryDir && EXECUTABLES.contains(exe.name)) {
val errno = JniHelper.sigkill(process.name.toInt()) val errno = JniHelper.sigkill(process.name.toInt())
if (errno != 0) Log.w("kill", "SIGKILL ${exe.absolutePath} (${process.name}) failed with $errno") if (errno != 0) Log.w("kill", "SIGKILL ${exe.absolutePath} (${process.name}) failed with $errno")
......
...@@ -62,7 +62,7 @@ class GuardedProcess(private val cmd: List<String>) { ...@@ -62,7 +62,7 @@ class GuardedProcess(private val cmd: List<String>) {
process = ProcessBuilder(cmd) process = ProcessBuilder(cmd)
.redirectErrorStream(true) .redirectErrorStream(true)
.directory(app.filesDir) .directory(app.deviceContext.filesDir)
.start() .start()
streamLogger(process.inputStream, Log::i) streamLogger(process.inputStream, Log::i)
......
...@@ -44,6 +44,7 @@ object LocalDnsService { ...@@ -44,6 +44,7 @@ object LocalDnsService {
override fun startNativeProcesses() { override fun startNativeProcesses() {
super.startNativeProcesses() super.startNativeProcesses()
val data = data
val profile = data.profile!! val profile = data.profile!!
fun makeDns(name: String, address: String, edns: Boolean = true): JSONObject { fun makeDns(name: String, address: String, edns: Boolean = true): JSONObject {
...@@ -53,7 +54,7 @@ object LocalDnsService { ...@@ -53,7 +54,7 @@ object LocalDnsService {
is Inet6Address -> "[$address]" is Inet6Address -> "[$address]"
else -> address else -> address
}) + ":53") }) + ":53")
.put("Timeout", 6) .put("Timeout", 3)
.put("EDNSClientSubnet", JSONObject().put("Policy", "disable")) .put("EDNSClientSubnet", JSONObject().put("Policy", "disable"))
if (edns) dns if (edns) dns
.put("Protocol", "tcp") .put("Protocol", "tcp")
...@@ -67,9 +68,9 @@ object LocalDnsService { ...@@ -67,9 +68,9 @@ object LocalDnsService {
val config = JSONObject() val config = JSONObject()
.put("BindAddress", "127.0.0.1:" + DataStore.portLocalDns) .put("BindAddress", "127.0.0.1:" + DataStore.portLocalDns)
.put("RedirectIPv6Record", true) .put("RedirectIPv6Record", true)
.put("DomainBase64Decode", true) .put("DomainBase64Decode", false)
.put("HostsFile", "hosts") .put("HostsFile", "hosts")
.put("MinimumTTL", 3600) .put("MinimumTTL", 120)
.put("CacheSize", 4096) .put("CacheSize", 4096)
val remoteDns = JSONArray(profile.remoteDns.split(",") val remoteDns = JSONArray(profile.remoteDns.split(",")
.mapIndexed { i, dns -> makeDns("UserDef-" + i, dns.trim()) }) .mapIndexed { i, dns -> makeDns("UserDef-" + i, dns.trim()) })
...@@ -78,18 +79,12 @@ object LocalDnsService { ...@@ -78,18 +79,12 @@ object LocalDnsService {
makeDns("Primary-2", "114.114.114.114", false) makeDns("Primary-2", "114.114.114.114", false)
)) ))
this as Context
try {
val localLinkDns = Dns.getDnsResolver(this)
localDns.put(makeDns("Primary-3", localLinkDns, false))
} catch (_: Exception) { } // ignore
when (profile.route) { when (profile.route) {
Acl.BYPASS_CHN, Acl.BYPASS_LAN_CHN, Acl.GFWLIST, Acl.CUSTOM_RULES -> config Acl.BYPASS_CHN, Acl.BYPASS_LAN_CHN, Acl.GFWLIST, Acl.CUSTOM_RULES -> config
.put("PrimaryDNS", localDns) .put("PrimaryDNS", localDns)
.put("AlternativeDNS", remoteDns) .put("AlternativeDNS", remoteDns)
.put("IPNetworkFile", "china_ip_list.txt") .put("IPNetworkFile", "china_ip_list.txt")
.put("DomainFile", "gfwlist.txt") .put("DomainFile", data.aclFile!!.absolutePath)
Acl.CHINALIST -> config Acl.CHINALIST -> config
.put("PrimaryDNS", localDns) .put("PrimaryDNS", localDns)
.put("AlternativeDNS", remoteDns) .put("AlternativeDNS", remoteDns)
...@@ -98,7 +93,7 @@ object LocalDnsService { ...@@ -98,7 +93,7 @@ object LocalDnsService {
// no need to setup AlternativeDNS in Acl.ALL/BYPASS_LAN mode // no need to setup AlternativeDNS in Acl.ALL/BYPASS_LAN mode
.put("OnlyPrimaryDNS", true) .put("OnlyPrimaryDNS", true)
} }
File(filesDir, file).bufferedWriter().use { it.write(config.toString()) } File(app.deviceContext.filesDir, file).writeText(config.toString())
return file return file
} }
......
...@@ -41,7 +41,9 @@ abstract class LocalSocketListener(protected val tag: String) : Thread() { ...@@ -41,7 +41,9 @@ abstract class LocalSocketListener(protected val tag: String) : Thread() {
* Inherited class do not need to close input/output streams as they will be closed automatically. * Inherited class do not need to close input/output streams as they will be closed automatically.
*/ */
protected abstract fun accept(socket: LocalSocket) protected abstract fun accept(socket: LocalSocket)
override final fun run() = LocalSocket().use { localSocket -> override final fun run() {
socketFile.delete() // It's a must-have to close and reuse previous local socket.
LocalSocket().use { localSocket ->
val serverSocket = try { val serverSocket = try {
localSocket.bind(LocalSocketAddress(socketFile.absolutePath, LocalSocketAddress.Namespace.FILESYSTEM)) localSocket.bind(LocalSocketAddress(socketFile.absolutePath, LocalSocketAddress.Namespace.FILESYSTEM))
LocalServerSocket(localSocket.fileDescriptor) LocalServerSocket(localSocket.fileDescriptor)
...@@ -59,6 +61,7 @@ abstract class LocalSocketListener(protected val tag: String) : Thread() { ...@@ -59,6 +61,7 @@ abstract class LocalSocketListener(protected val tag: String) : Thread() {
}?.use(this::accept) }?.use(this::accept)
} }
} }
}
fun stopThread() { fun stopThread() {
running = false running = false
......
...@@ -23,6 +23,7 @@ package com.github.shadowsocks.bg ...@@ -23,6 +23,7 @@ package com.github.shadowsocks.bg
import android.app.Service import android.app.Service
import android.content.Intent import android.content.Intent
import android.os.IBinder import android.os.IBinder
import com.github.shadowsocks.database.Profile
/** /**
* Shadowsocks service at its minimum. * Shadowsocks service at its minimum.
...@@ -33,8 +34,8 @@ class ProxyService : Service(), BaseService.Interface { ...@@ -33,8 +34,8 @@ class ProxyService : Service(), BaseService.Interface {
} }
override val tag: String get() = "ShadowsocksProxyService" override val tag: String get() = "ShadowsocksProxyService"
override fun createNotification(): ServiceNotification = override fun createNotification(profileName: String): ServiceNotification =
ServiceNotification(this, data.profile!!.formattedName, "service-proxy", true) ServiceNotification(this, profileName, "service-proxy", true)
override fun onBind(intent: Intent): IBinder? = super.onBind(intent) override fun onBind(intent: Intent): IBinder? = super.onBind(intent)
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int = override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int =
......
...@@ -38,6 +38,12 @@ import com.github.shadowsocks.utils.Action ...@@ -38,6 +38,12 @@ import com.github.shadowsocks.utils.Action
import com.github.shadowsocks.utils.broadcastReceiver import com.github.shadowsocks.utils.broadcastReceiver
import java.util.* import java.util.*
/**
* Android < 8 VPN: always invisible because of VPN notification/icon
* 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)
*/
class ServiceNotification(private val service: BaseService.Interface, profileName: String, class ServiceNotification(private val service: BaseService.Interface, profileName: String,
channel: String, private val visible: Boolean = false) { channel: String, private val visible: Boolean = false) {
private val keyGuard = (service as Context).getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager private val keyGuard = (service as Context).getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
...@@ -69,7 +75,7 @@ class ServiceNotification(private val service: BaseService.Interface, profileNam ...@@ -69,7 +75,7 @@ class ServiceNotification(private val service: BaseService.Interface, profileNam
.setContentTitle(profileName) .setContentTitle(profileName)
.setContentIntent(PendingIntent.getActivity(service, 0, Intent(service, MainActivity::class.java) .setContentIntent(PendingIntent.getActivity(service, 0, Intent(service, MainActivity::class.java)
.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT), 0)) .setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT), 0))
.setSmallIcon(R.drawable.ic_start_connected) .setSmallIcon(R.drawable.ic_service_active)
private val style = NotificationCompat.BigTextStyle(builder) private val style = NotificationCompat.BigTextStyle(builder)
private var isVisible = true private var isVisible = true
...@@ -78,24 +84,22 @@ class ServiceNotification(private val service: BaseService.Interface, profileNam ...@@ -78,24 +84,22 @@ class ServiceNotification(private val service: BaseService.Interface, profileNam
if (Build.VERSION.SDK_INT < 24) builder.addAction(R.drawable.ic_navigation_close, 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)) service.getString(R.string.stop), PendingIntent.getBroadcast(service, 0, Intent(Action.CLOSE), 0))
val power = service.getSystemService(Context.POWER_SERVICE) as PowerManager val power = service.getSystemService(Context.POWER_SERVICE) as PowerManager
update(if (if (Build.VERSION.SDK_INT >= 20) power.isInteractive else @Suppress("DEPRECATION") power.isScreenOn) update(if (power.isInteractive) Intent.ACTION_SCREEN_ON else Intent.ACTION_SCREEN_OFF, true)
Intent.ACTION_SCREEN_ON else Intent.ACTION_SCREEN_OFF, true)
val screenFilter = IntentFilter() val screenFilter = IntentFilter()
screenFilter.addAction(Intent.ACTION_SCREEN_ON) screenFilter.addAction(Intent.ACTION_SCREEN_ON)
screenFilter.addAction(Intent.ACTION_SCREEN_OFF) 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) service.registerReceiver(lockReceiver, screenFilter)
} }
private fun update(action: String, forceShow: Boolean = false) { private fun update(action: String, forceShow: Boolean = false) {
if (forceShow || service.data.state == BaseService.CONNECTED) when (action) { if (forceShow || service.data.state == BaseService.CONNECTED) when (action) {
Intent.ACTION_SCREEN_OFF -> { Intent.ACTION_SCREEN_OFF -> {
setVisible(visible && Build.VERSION.SDK_INT < 21, forceShow) setVisible(false, forceShow)
unregisterCallback() // unregister callback to save battery unregisterCallback() // unregister callback to save battery
} }
Intent.ACTION_SCREEN_ON -> { Intent.ACTION_SCREEN_ON -> {
setVisible(visible && (Build.VERSION.SDK_INT < 21 || !keyGuard.inKeyguardRestrictedInputMode()), setVisible(visible && !keyGuard.inKeyguardRestrictedInputMode(), forceShow)
forceShow)
service.data.binder.registerCallback(callback) service.data.binder.registerCallback(callback)
service.data.binder.startListeningForBandwidth(callback) service.data.binder.startListeningForBandwidth(callback)
callbackRegistered = true callbackRegistered = true
......
...@@ -20,8 +20,9 @@ ...@@ -20,8 +20,9 @@
package com.github.shadowsocks.bg package com.github.shadowsocks.bg
import android.app.KeyguardManager
import android.content.Context
import android.graphics.drawable.Icon import android.graphics.drawable.Icon
import android.os.Build
import android.service.quicksettings.Tile import android.service.quicksettings.Tile
import android.support.annotation.RequiresApi import android.support.annotation.RequiresApi
import com.github.shadowsocks.App.Companion.app import com.github.shadowsocks.App.Companion.app
...@@ -31,34 +32,35 @@ import com.github.shadowsocks.aidl.IShadowsocksService ...@@ -31,34 +32,35 @@ import com.github.shadowsocks.aidl.IShadowsocksService
import com.github.shadowsocks.aidl.IShadowsocksServiceCallback import com.github.shadowsocks.aidl.IShadowsocksServiceCallback
import android.service.quicksettings.TileService as BaseTileService import android.service.quicksettings.TileService as BaseTileService
@RequiresApi(Build.VERSION_CODES.N) @RequiresApi(24)
class TileService : BaseTileService(), ShadowsocksConnection.Interface { class TileService : BaseTileService(), ShadowsocksConnection.Interface {
private val iconIdle by lazy { Icon.createWithResource(this, R.drawable.ic_start_idle).setTint(0x79ffffff) } private val iconIdle by lazy { Icon.createWithResource(this, R.drawable.ic_service_idle).setTint(0x79ffffff) }
private val iconBusy by lazy { Icon.createWithResource(this, R.drawable.ic_start_busy) } private val iconBusy by lazy { Icon.createWithResource(this, R.drawable.ic_service_busy) }
private val iconConnected by lazy { Icon.createWithResource(this, R.drawable.ic_start_connected) } private val iconConnected by lazy { Icon.createWithResource(this, R.drawable.ic_service_active) }
private val keyguard by lazy { getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager }
override val serviceCallback: IShadowsocksServiceCallback.Stub by lazy { override val serviceCallback: IShadowsocksServiceCallback.Stub by lazy {
@RequiresApi(Build.VERSION_CODES.N) @RequiresApi(24)
object : IShadowsocksServiceCallback.Stub() { object : IShadowsocksServiceCallback.Stub() {
override fun stateChanged(state: Int, profileName: String?, msg: String?) { override fun stateChanged(state: Int, profileName: String?, msg: String?) {
val tile = qsTile ?: return val tile = qsTile ?: return
var label: String? = null
when (state) { when (state) {
BaseService.STOPPED -> { BaseService.STOPPED -> {
tile.icon = iconIdle tile.icon = iconIdle
tile.label = getString(R.string.app_name)
tile.state = Tile.STATE_INACTIVE tile.state = Tile.STATE_INACTIVE
} }
BaseService.CONNECTED -> { BaseService.CONNECTED -> {
tile.icon = iconConnected tile.icon = iconConnected
tile.label = profileName ?: getString(R.string.app_name) if (!keyguard.isDeviceLocked) label = profileName
tile.state = Tile.STATE_ACTIVE tile.state = Tile.STATE_ACTIVE
} }
else -> { else -> {
tile.icon = iconBusy tile.icon = iconBusy
tile.label = getString(R.string.app_name)
tile.state = Tile.STATE_UNAVAILABLE tile.state = Tile.STATE_UNAVAILABLE
} }
} }
tile.label = label ?: getString(R.string.app_name)
tile.updateTile() tile.updateTile()
} }
override fun trafficUpdated(profileId: Int, txRate: Long, rxRate: Long, txTotal: Long, rxTotal: Long) { } override fun trafficUpdated(profileId: Int, txRate: Long, rxRate: Long, txTotal: Long, rxTotal: Long) { }
......
...@@ -29,7 +29,7 @@ import java.nio.ByteBuffer ...@@ -29,7 +29,7 @@ import java.nio.ByteBuffer
import java.nio.ByteOrder import java.nio.ByteOrder
class TrafficMonitorThread : LocalSocketListener("TrafficMonitorThread") { class TrafficMonitorThread : LocalSocketListener("TrafficMonitorThread") {
override val socketFile = File(app.filesDir, "/stat_path") override val socketFile = File(app.deviceContext.filesDir, "stat_path")
override fun accept(socket: LocalSocket) { override fun accept(socket: LocalSocket) {
try { try {
......
...@@ -23,6 +23,7 @@ package com.github.shadowsocks.bg ...@@ -23,6 +23,7 @@ package com.github.shadowsocks.bg
import android.app.Service import android.app.Service
import android.content.Intent import android.content.Intent
import android.os.IBinder import android.os.IBinder
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.preference.DataStore import com.github.shadowsocks.preference.DataStore
import java.io.File import java.io.File
...@@ -32,8 +33,8 @@ class TransproxyService : Service(), LocalDnsService.Interface { ...@@ -32,8 +33,8 @@ class TransproxyService : Service(), LocalDnsService.Interface {
} }
override val tag: String get() = "ShadowsocksTransproxyService" override val tag: String get() = "ShadowsocksTransproxyService"
override fun createNotification(): ServiceNotification = override fun createNotification(profileName: String): ServiceNotification =
ServiceNotification(this, data.profile!!.formattedName, "service-transproxy", true) ServiceNotification(this, profileName, "service-transproxy", true)
override fun onBind(intent: Intent): IBinder? = super.onBind(intent) override fun onBind(intent: Intent): IBinder? = super.onBind(intent)
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int = override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int =
...@@ -49,12 +50,12 @@ class TransproxyService : Service(), LocalDnsService.Interface { ...@@ -49,12 +50,12 @@ class TransproxyService : Service(), LocalDnsService.Interface {
"-u", "-u",
"-l", DataStore.portLocalDns.toString(), // ss-tunnel listens on the same port as overture "-l", DataStore.portLocalDns.toString(), // ss-tunnel listens on the same port as overture
"-L", data.profile!!.remoteDns.split(",").first().trim() + ":53", "-L", data.profile!!.remoteDns.split(",").first().trim() + ":53",
"-c", "shadowsocks.json") // config is already built by BaseService.Interface "-c", data.shadowsocksConfigFile!!.absolutePath) // config is already built by BaseService.Interface
sstunnelProcess = GuardedProcess(cmd).start() sstunnelProcess = GuardedProcess(cmd).start()
} }
private fun startRedsocksDaemon() { private fun startRedsocksDaemon() {
val config = """base { File(app.deviceContext.filesDir, "redsocks.conf").writeText("""base {
log_debug = off; log_debug = off;
log_info = off; log_info = off;
log = stderr; log = stderr;
...@@ -68,8 +69,7 @@ redsocks { ...@@ -68,8 +69,7 @@ redsocks {
port = ${DataStore.portProxy}; port = ${DataStore.portProxy};
type = socks5; type = socks5;
} }
""" """)
File(filesDir, "redsocks.conf").bufferedWriter().use { it.write(config) }
redsocksProcess = GuardedProcess(arrayListOf( redsocksProcess = GuardedProcess(arrayListOf(
File(applicationInfo.nativeLibraryDir, Executable.REDSOCKS).absolutePath, File(applicationInfo.nativeLibraryDir, Executable.REDSOCKS).absolutePath,
"-c", "redsocks.conf") "-c", "redsocks.conf")
......
...@@ -24,7 +24,6 @@ import android.app.Service ...@@ -24,7 +24,6 @@ import android.app.Service
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.net.LocalSocket import android.net.LocalSocket
import android.os.Build
import android.os.IBinder import android.os.IBinder
import android.os.ParcelFileDescriptor import android.os.ParcelFileDescriptor
import android.util.Log import android.util.Log
...@@ -52,7 +51,7 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface { ...@@ -52,7 +51,7 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
} }
private inner class ProtectWorker : LocalSocketListener("ShadowsocksVpnThread") { private inner class ProtectWorker : LocalSocketListener("ShadowsocksVpnThread") {
override val socketFile: File = File(filesDir, "protect_path") override val socketFile: File = File(app.deviceContext.filesDir, "protect_path")
override fun accept(socket: LocalSocket) { override fun accept(socket: LocalSocket) {
try { try {
...@@ -76,8 +75,8 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface { ...@@ -76,8 +75,8 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
} }
override val tag: String get() = "ShadowsocksVpnService" override val tag: String get() = "ShadowsocksVpnService"
override fun createNotification(): ServiceNotification = override fun createNotification(profileName: String): ServiceNotification =
ServiceNotification(this, data.profile!!.formattedName, "service-vpn") ServiceNotification(this, profileName, "service-vpn")
private var conn: ParcelFileDescriptor? = null private var conn: ParcelFileDescriptor? = null
private var worker: ProtectWorker? = null private var worker: ProtectWorker? = null
...@@ -140,22 +139,29 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface { ...@@ -140,22 +139,29 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
builder.addRoute("::", 0) builder.addRoute("::", 0)
} }
if (Build.VERSION.SDK_INT >= 21 && profile.proxyApps) { if (profile.proxyApps) {
for (pkg in profile.individual.split('\n')) try { val me = packageName
if (profile.bypass) builder.addDisallowedApplication(pkg) else builder.addAllowedApplication(pkg) profile.individual.split('\n')
.filter { it != me }
.forEach {
try {
if (profile.bypass) builder.addDisallowedApplication(it)
else builder.addAllowedApplication(it)
} catch (ex: PackageManager.NameNotFoundException) { } catch (ex: PackageManager.NameNotFoundException) {
Log.e(tag, "Invalid package name", ex) Log.e(tag, "Invalid package name", ex)
} }
} }
if (!profile.bypass) builder.addAllowedApplication(me)
}
when (profile.route) { when (profile.route) {
Acl.ALL, Acl.BYPASS_CHN, Acl.CUSTOM_RULES -> builder.addRoute("0.0.0.0", 0) Acl.ALL, Acl.BYPASS_CHN, Acl.CUSTOM_RULES -> builder.addRoute("0.0.0.0", 0)
else -> { else -> {
resources.getStringArray(R.array.bypass_private_route).forEach { resources.getStringArray(R.array.bypass_private_route).forEach {
val subnet = Subnet.fromString(it) val subnet = Subnet.fromString(it)!!
builder.addRoute(subnet.address.hostAddress, subnet.prefixSize) builder.addRoute(subnet.address.hostAddress, subnet.prefixSize)
} }
profile.remoteDns.split(",").map { it.trim().parseNumericAddress() } profile.remoteDns.split(",").mapNotNull { it.trim().parseNumericAddress() }
.forEach { builder.addRoute(it, it.address.size shl 3) } .forEach { builder.addRoute(it, it.address.size shl 3) }
} }
} }
...@@ -187,10 +193,10 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface { ...@@ -187,10 +193,10 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
private fun sendFd(fd: Int): Boolean { private fun sendFd(fd: Int): Boolean {
if (fd != -1) { if (fd != -1) {
var tries = 1 var tries = 0
while (tries < 5) { while (tries < 10) {
Thread.sleep(1000L * tries) Thread.sleep(30L shl tries)
if (JniHelper.sendFd(fd, File(filesDir, "sock_path").absolutePath) != -1) return true if (JniHelper.sendFd(fd, File(app.deviceContext.filesDir, "sock_path").absolutePath) != -1) return true
tries += 1 tries += 1
} }
} }
......
...@@ -53,7 +53,7 @@ class KeyValuePair() { ...@@ -53,7 +53,7 @@ class KeyValuePair() {
get() = if (valueType == TYPE_LONG) ByteBuffer.wrap(value).long else null get() = if (valueType == TYPE_LONG) ByteBuffer.wrap(value).long else null
val string: String? val string: String?
get() = if (valueType == TYPE_STRING) String(value) else null get() = if (valueType == TYPE_STRING) String(value) else null
val stringSet: MutableSet<String>? val stringSet: Set<String>?
get() = if (valueType == TYPE_STRING_SET) { get() = if (valueType == TYPE_STRING_SET) {
val buffer = ByteBuffer.wrap(value) val buffer = ByteBuffer.wrap(value)
val result = HashSet<String>() val result = HashSet<String>()
...@@ -95,7 +95,7 @@ class KeyValuePair() { ...@@ -95,7 +95,7 @@ class KeyValuePair() {
this.value = value.toByteArray() this.value = value.toByteArray()
return this return this
} }
fun put(value: MutableSet<String>): KeyValuePair { fun put(value: Set<String>): KeyValuePair {
valueType = TYPE_STRING_SET valueType = TYPE_STRING_SET
val stream = ByteArrayOutputStream() val stream = ByteArrayOutputStream()
for (v in value) { for (v in value) {
......
...@@ -31,7 +31,7 @@ import com.j256.ormlite.dao.Dao ...@@ -31,7 +31,7 @@ import com.j256.ormlite.dao.Dao
import com.j256.ormlite.support.ConnectionSource import com.j256.ormlite.support.ConnectionSource
import com.j256.ormlite.table.TableUtils import com.j256.ormlite.table.TableUtils
object DBHelper : OrmLiteSqliteOpenHelper(app, Key.PROFILE, null, 24) { object PrivateDatabase : OrmLiteSqliteOpenHelper(app, Key.DB_PROFILE, null, 25) {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
val profileDao: Dao<Profile, Int> by lazy { getDao(Profile::class.java) as Dao<Profile, Int> } val profileDao: Dao<Profile, Int> by lazy { getDao(Profile::class.java) as Dao<Profile, Int> }
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
...@@ -134,8 +134,12 @@ object DBHelper : OrmLiteSqliteOpenHelper(app, Key.PROFILE, null, 24) { ...@@ -134,8 +134,12 @@ object DBHelper : OrmLiteSqliteOpenHelper(app, Key.PROFILE, null, 24) {
TableUtils.createTable(connectionSource, KeyValuePair::class.java) TableUtils.createTable(connectionSource, KeyValuePair::class.java)
profileDao.executeRawNoArgs("COMMIT;") profileDao.executeRawNoArgs("COMMIT;")
val old = PreferenceManager.getDefaultSharedPreferences(app) val old = PreferenceManager.getDefaultSharedPreferences(app)
kvPairDao.createOrUpdate(KeyValuePair(Key.id).put(old.getInt(Key.id, 0))) PublicDatabase.kvPairDao.createOrUpdate(KeyValuePair(Key.id).put(old.getInt(Key.id, 0)))
kvPairDao.createOrUpdate(KeyValuePair(Key.tfo).put(old.getBoolean(Key.tfo, false))) PublicDatabase.kvPairDao.createOrUpdate(KeyValuePair(Key.tfo).put(old.getBoolean(Key.tfo, false)))
}
if (oldVersion < 25) {
PublicDatabase.onUpgrade(database, 0, -1)
} }
} catch (ex: Exception) { } catch (ex: Exception) {
app.track(ex) app.track(ex)
......
...@@ -29,10 +29,11 @@ import com.github.shadowsocks.utils.Key ...@@ -29,10 +29,11 @@ import com.github.shadowsocks.utils.Key
import com.github.shadowsocks.utils.parsePort import com.github.shadowsocks.utils.parsePort
import com.j256.ormlite.field.DataType import com.j256.ormlite.field.DataType
import com.j256.ormlite.field.DatabaseField import com.j256.ormlite.field.DatabaseField
import java.io.Serializable
import java.net.URI import java.net.URI
import java.util.* import java.util.*
class Profile { class Profile : Serializable {
companion object { companion object {
private const val TAG = "ShadowParser" private const val TAG = "ShadowParser"
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()
...@@ -153,34 +154,34 @@ class Profile { ...@@ -153,34 +154,34 @@ class Profile {
override fun toString() = toUri().toString() override fun toString() = toUri().toString()
fun serialize() { fun serialize() {
DataStore.putString(Key.name, name) DataStore.privateStore.putString(Key.name, name)
DataStore.putString(Key.host, host) DataStore.privateStore.putString(Key.host, host)
DataStore.putString(Key.remotePort, remotePort.toString()) DataStore.privateStore.putString(Key.remotePort, remotePort.toString())
DataStore.putString(Key.password, password) DataStore.privateStore.putString(Key.password, password)
DataStore.putString(Key.route, route) DataStore.privateStore.putString(Key.route, route)
DataStore.putString(Key.remoteDns, remoteDns) DataStore.privateStore.putString(Key.remoteDns, remoteDns)
DataStore.putString(Key.method, method) DataStore.privateStore.putString(Key.method, method)
DataStore.proxyApps = proxyApps DataStore.proxyApps = proxyApps
DataStore.bypass = bypass DataStore.bypass = bypass
DataStore.putBoolean(Key.udpdns, udpdns) DataStore.privateStore.putBoolean(Key.udpdns, udpdns)
DataStore.putBoolean(Key.ipv6, ipv6) DataStore.privateStore.putBoolean(Key.ipv6, ipv6)
DataStore.individual = individual DataStore.individual = individual
DataStore.plugin = plugin ?: "" DataStore.plugin = plugin ?: ""
DataStore.remove(Key.dirty) DataStore.privateStore.remove(Key.dirty)
} }
fun deserialize() { fun deserialize() {
// It's assumed that default values are never used, so 0/false/null is always used even if that isn't the case // It's assumed that default values are never used, so 0/false/null is always used even if that isn't the case
name = DataStore.getString(Key.name) ?: "" name = DataStore.privateStore.getString(Key.name) ?: ""
host = DataStore.getString(Key.host) ?: "" host = DataStore.privateStore.getString(Key.host) ?: ""
remotePort = parsePort(DataStore.getString(Key.remotePort), 8388, 1) remotePort = parsePort(DataStore.privateStore.getString(Key.remotePort), 8388, 1)
password = DataStore.getString(Key.password) ?: "" password = DataStore.privateStore.getString(Key.password) ?: ""
method = DataStore.getString(Key.method) ?: "" method = DataStore.privateStore.getString(Key.method) ?: ""
route = DataStore.getString(Key.route) ?: "" route = DataStore.privateStore.getString(Key.route) ?: ""
remoteDns = DataStore.getString(Key.remoteDns) ?: "" remoteDns = DataStore.privateStore.getString(Key.remoteDns) ?: ""
proxyApps = DataStore.proxyApps proxyApps = DataStore.proxyApps
bypass = DataStore.bypass bypass = DataStore.bypass
udpdns = DataStore.getBoolean(Key.udpdns, false) udpdns = DataStore.privateStore.getBoolean(Key.udpdns, false)
ipv6 = DataStore.getBoolean(Key.ipv6, false) ipv6 = DataStore.privateStore.getBoolean(Key.ipv6, false)
individual = DataStore.individual individual = DataStore.individual
plugin = DataStore.plugin plugin = DataStore.plugin
} }
......
...@@ -23,6 +23,8 @@ package com.github.shadowsocks.database ...@@ -23,6 +23,8 @@ package com.github.shadowsocks.database
import android.util.Log import android.util.Log
import com.github.shadowsocks.App.Companion.app import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.ProfilesFragment import com.github.shadowsocks.ProfilesFragment
import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.DirectBoot
object ProfileManager { object ProfileManager {
private const val TAG = "ProfileManager" private const val TAG = "ProfileManager"
...@@ -41,10 +43,10 @@ object ProfileManager { ...@@ -41,10 +43,10 @@ object ProfileManager {
profile.individual = oldProfile.individual profile.individual = oldProfile.individual
profile.udpdns = oldProfile.udpdns profile.udpdns = oldProfile.udpdns
} }
val last = DBHelper.profileDao.queryRaw(DBHelper.profileDao.queryBuilder().selectRaw("MAX(userOrder)") val last = PrivateDatabase.profileDao.queryRaw(PrivateDatabase.profileDao.queryBuilder()
.prepareStatementString()).firstResult .selectRaw("MAX(userOrder)").prepareStatementString()).firstResult
if (last != null && last.size == 1 && last[0] != null) profile.userOrder = last[0].toLong() + 1 if (last != null && last.size == 1 && last[0] != null) profile.userOrder = last[0].toLong() + 1
DBHelper.profileDao.createOrUpdate(profile) PrivateDatabase.profileDao.createOrUpdate(profile)
ProfilesFragment.instance?.profilesAdapter?.add(profile) ProfilesFragment.instance?.profilesAdapter?.add(profile)
} catch (ex: Exception) { } catch (ex: Exception) {
Log.e(TAG, "addProfile", ex) Log.e(TAG, "addProfile", ex)
...@@ -53,8 +55,11 @@ object ProfileManager { ...@@ -53,8 +55,11 @@ object ProfileManager {
return profile return profile
} }
/**
* Note: It's caller's responsibility to update DirectBoot profile if necessary.
*/
fun updateProfile(profile: Profile): Boolean = try { fun updateProfile(profile: Profile): Boolean = try {
DBHelper.profileDao.update(profile) PrivateDatabase.profileDao.update(profile)
true true
} catch (ex: Exception) { } catch (ex: Exception) {
Log.e(TAG, "updateProfile", ex) Log.e(TAG, "updateProfile", ex)
...@@ -63,7 +68,7 @@ object ProfileManager { ...@@ -63,7 +68,7 @@ object ProfileManager {
} }
fun getProfile(id: Int): Profile? = try { fun getProfile(id: Int): Profile? = try {
DBHelper.profileDao.queryForId(id) PrivateDatabase.profileDao.queryForId(id)
} catch (ex: Exception) { } catch (ex: Exception) {
Log.e(TAG, "getProfile", ex) Log.e(TAG, "getProfile", ex)
app.track(ex) app.track(ex)
...@@ -71,8 +76,9 @@ object ProfileManager { ...@@ -71,8 +76,9 @@ object ProfileManager {
} }
fun delProfile(id: Int): Boolean = try { fun delProfile(id: Int): Boolean = try {
DBHelper.profileDao.deleteById(id) PrivateDatabase.profileDao.deleteById(id)
ProfilesFragment.instance?.profilesAdapter?.removeId(id) ProfilesFragment.instance?.profilesAdapter?.removeId(id)
if (id == DataStore.profileId && DataStore.directBootAware) DirectBoot.clean()
true true
} catch (ex: Exception) { } catch (ex: Exception) {
Log.e(TAG, "delProfile", ex) Log.e(TAG, "delProfile", ex)
...@@ -81,7 +87,7 @@ object ProfileManager { ...@@ -81,7 +87,7 @@ object ProfileManager {
} }
fun getFirstProfile(): Profile? = try { fun getFirstProfile(): Profile? = try {
val result = DBHelper.profileDao.query(DBHelper.profileDao.queryBuilder().limit(1L).prepare()) val result = PrivateDatabase.profileDao.query(PrivateDatabase.profileDao.queryBuilder().limit(1L).prepare())
if (result.size == 1) result[0] else null if (result.size == 1) result[0] else null
} catch (ex: Exception) { } catch (ex: Exception) {
Log.e(TAG, "getAllProfiles", ex) Log.e(TAG, "getAllProfiles", ex)
...@@ -90,7 +96,7 @@ object ProfileManager { ...@@ -90,7 +96,7 @@ object ProfileManager {
} }
fun getAllProfiles(): List<Profile>? = try { fun getAllProfiles(): List<Profile>? = try {
DBHelper.profileDao.query(DBHelper.profileDao.queryBuilder().orderBy("userOrder", true).prepare()) PrivateDatabase.profileDao.query(PrivateDatabase.profileDao.queryBuilder().orderBy("userOrder", true).prepare())
} catch (ex: Exception) { } catch (ex: Exception) {
Log.e(TAG, "getAllProfiles", ex) Log.e(TAG, "getAllProfiles", ex)
app.track(ex) app.track(ex)
......
...@@ -50,7 +50,7 @@ class PluginConfiguration(val pluginsOptions: Map<String, PluginOptions>, val se ...@@ -50,7 +50,7 @@ class PluginConfiguration(val pluginsOptions: Map<String, PluginOptions>, val se
}) })
fun getOptions(id: String): PluginOptions = if (id.isEmpty()) PluginOptions() else fun getOptions(id: String): PluginOptions = if (id.isEmpty()) PluginOptions() else
pluginsOptions.get(id) ?: PluginOptions(id, PluginManager.fetchPlugins()[id]?.defaultConfig) pluginsOptions[id] ?: PluginOptions(id, PluginManager.fetchPlugins()[id]?.defaultConfig)
val selectedOptions: PluginOptions get() = getOptions(selected) val selectedOptions: PluginOptions get() = getOptions(selected)
override fun toString(): String { override fun toString(): String {
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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