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

Merge pull request #5 from shadowsocks/master

Sync
parents 9c7f3867 8335a580
FROM circleci/android:api-28-ndk
RUN \
wget -O /tmp/go.tgz "https://golang.org/dl/go1.11.linux-amd64.tar.gz"; \
sudo tar -C /usr/local -xzf /tmp/go.tgz; \
rm /tmp/go.tgz
ENV PATH /usr/local/go/bin:$PATH
version: 2
jobs:
build:
working_directory: ~/code
docker:
- image: shadowsocks/shadowsocks-android:circleci
environment:
JVM_OPTS: -Xmx3500m
GRADLE_OPTS: -Dorg.gradle.workers.max=1 -Dorg.gradle.daemon=false -Dkotlin.compiler.execution.strategy="in-process"
steps:
- checkout
- run: git submodule update --init --recursive
- run: pushd core/src/overture; ./make.bash 21; popd
- restore_cache:
key: jars-{{ checksum "build.gradle" }}
- run:
name: Run Build and Tests
command: ./gradlew assembleDebug check
- save_cache:
paths:
- ~/.gradle
- ~/.android/build-cache
key: jars-{{ checksum "build.gradle" }}
- store_artifacts:
path: mobile/build/outputs/apk
destination: apk/mobile
- store_artifacts:
path: tv/build/outputs/apk
destination: apk/tv
- store_artifacts:
path: core/build/reports
destination: reports/core
- store_artifacts:
path: mobile/build/reports
destination: reports/mobile
- store_artifacts:
path: plugin/build/reports
destination: reports/plugin
- store_artifacts:
path: tv/build/reports
destination: reports/tv
Please read FAQ then answer these questions before submitting your issue. Thanks!
---
name: Bug report
about: Create a report to help us improve
### Environment
---
* Android version: *e.g. "7.0.0_r14", more detailed description is preferred*
* Device: *e.g. Google Pixel XL*
* Shadowsocks version: *version code "v3.0.0" or commit ID "a073f85"*
* Last version that did not exhibit the issue (if applicable):
Please read contributing guidelines. Thanks.
### Configuration
**Describe the bug**
A clear and concise description of what the bug is.
_Put an `x` inside the [ ] that applies._
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Smartphone (please complete the following information):**
- Android version: [e.g. "7.0.0_r14", more detailed description is preferred]
- Device: [e.g. Google Pixel XL]
- Version: [e.g. version code "v3.0.0" or commit ID "a073f85"]
- Last version that did not exhibit the issue: [not applicable]
**Configuration**
Put an `x` inside the [ ] that applies.
* [ ] IPv4 server address
* [ ] IPv6 server address
......@@ -34,11 +54,5 @@ _Put an `x` inside the [ ] that applies._
* [ ] TCP Fast Open
* If you're not using VPN mode, please supply more details here:
### What did you do?
### What did you expect to see?
### What did you see instead?
**Additional context**
Add any other context about the problem here.
---
name: Feature request
about: Suggest an idea for this project
---
Please read contributing guidelines. Thanks.
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
---
name: Questions/Support requests/Other
about: You should probably use the forum
---
Please use the forum at https://discourse.shadowsocks.org/c/implementations/shadowsocks-android instead. Your issue will probably be closed.
......@@ -26,7 +26,7 @@ The exclamation mark in the Wi-Fi/cellular icon appears because the system fails
1. Some ROM has broken VPNService implementation, especially for IPv6;
2. Some ROM has aggressive (or called broken) background service killing policy;
3. Some ROM like [Flyme](https://github.com/shadowsocks/shadowsocks-android/issues/1589) has **very** broken direct boot support;
3. Some ROM like [Flyme](https://github.com/shadowsocks/shadowsocks-android/issues/1821) is basically broken **in every way possible**;
4. If you have Xposed framework and/or battery saver apps, it's likely that this app wouldn't work well with these either.
* Fixes for MIUI: [#772](https://github.com/shadowsocks/shadowsocks-android/issues/772)
......@@ -36,6 +36,7 @@ The exclamation mark in the Wi-Fi/cellular icon appears because the system fails
* Samsung and/or Brevent: [#1410](https://github.com/shadowsocks/shadowsocks-android/issues/1410)
* Another Samsung: [#1712](https://github.com/shadowsocks/shadowsocks-android/issues/1712)
* Don't install this app on SD card because of permission issues: [#1124 (comment)](https://github.com/shadowsocks/shadowsocks-android/issues/1124#issuecomment-307556453)
* `INTERACT_ACROSS_USERS` permission missing: [#1184](https://github.com/shadowsocks/shadowsocks-android/issues/1184)
### How to pause Shadowsocks service?
......@@ -55,12 +56,6 @@ More details: https://kb.adguard.com/en/android/solving-problems/battery
Allow this app to consume background data in app settings.
### Why Camera permission is required on devices below Android 6.0?
To scan the QR code through the integrated QR scanner.
By the way, upgrade your Android system already.
### How to use Transproxy mode?
1. Install [AFWall+](https://github.com/ukanth/afwall);
......
### Type of changes
_Put an `x` inside the [ ] that applies._
* [ ] Bugfix
* [ ] New feature
* [ ] Translation
* [ ] General refinement
* Other:
### Details
Please provide more details about changes if necessary.
......@@ -14,16 +14,8 @@ core/src/overture/bin
core/src/overture/pkg
core/src/overture/src/github.com/Sirupsen
core/src/overture/src/github.com/mattn
core/src/overture/src/github.com/miekg
core/src/overture/src/github.com/shawn1m
core/src/overture/src/github.com/tools
core/src/overture/src/golang.org
core/src/overture/src/gopkg.in
# work in progress
tv/
things/
# release apks
*.apk
......@@ -30,9 +30,6 @@
[submodule "core/src/main/jni/libev"]
path = core/src/main/jni/libev
url = https://github.com/shadowsocks/libev.git
[submodule "core/src/overture/go"]
path = core/src/overture/go
url = https://github.com/shadowsocks/go.git
[submodule "core/src/overture/src/github.com/shadowsocks/overture"]
path = core/src/overture/src/github.com/shadowsocks/overture
url = https://github.com/shadowsocks/overture.git
language: android
sudo: required
jdk:
- oraclejdk8
addons:
artifacts: true
env:
global:
- GOROOT_BOOTSTRAP=$GOROOT
services:
- docker
before_cache:
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
- rm -fr $HOME/.gradle/caches/*/plugin-resolution/
cache:
directories:
- $HOME/.gradle/caches/
- $HOME/.gradle/wrapper/
install:
- pushd $ANDROID_HOME
- rm -rf tools
- wget -q https://dl.google.com/android/repository/sdk-tools-linux-4333796.zip -O tools.zip
- unzip -q tools.zip
- popd
- touch $HOME/.android/repositories.cfg
- yes | sdkmanager
"build-tools;27.0.3"
ndk-bundle
"platforms;android-27"
>/dev/null
before_install:
- docker pull shadowsocks/shadowsocks-android:circleci
- chmod -R a+wrx .
script:
- ./gradlew assembleDebug check
- docker run --rm -v ${PWD}:/build shadowsocks/shadowsocks-android:circleci bash -c "cd /build; ./gradlew assembleDebug check"
Please check [FAQ](.github/faq.md) before submitting new issues.
# Submitting Issues
And just in case you don't know,
[don't ask questions on issues](https://medium.com/@methane/why-you-must-not-ask-questions-on-github-issues-51d741d83fde).
* Questions and general discussions should go to [Discourse forum](https://discourse.shadowsocks.org/c/implementations/shadowsocks-android);
* Search for your issue here and at the forum and check [FAQ](https://github.com/shadowsocks/shadowsocks-android/blob/master/.github/faq.md) before submitting new issues;
* Always provide information asked in the template unless you know what you're doing;
* Logcat would also be helpful.
## Shadowsocks for Android
[![Build Status](https://api.travis-ci.org/shadowsocks/shadowsocks-android.svg)](https://travis-ci.org/shadowsocks/shadowsocks-android)
[![Build Status](https://travis-ci.com/shadowsocks/shadowsocks-android.svg?branch=master)](https://travis-ci.com/shadowsocks/shadowsocks-android)
[![API](https://img.shields.io/badge/API-21%2B-brightgreen.svg?style=flat)](https://android-arsenal.com/api?level=21)
[![Releases](https://img.shields.io/github/downloads/shadowsocks/shadowsocks-android/total.svg)](https://github.com/shadowsocks/shadowsocks-android/releases)
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/1a21d48d466644cdbcb57a1889abea5b)](https://www.codacy.com/app/shadowsocks/shadowsocks-android?utm_source=github.com&utm_medium=referral&utm_content=shadowsocks/shadowsocks-android&utm_campaign=Badge_Grade)
......@@ -13,9 +13,8 @@ A [shadowsocks](http://shadowsocks.org) client for Android, written in Kotlin.
### PREREQUISITES
* JDK 1.8
* Go 1.4+
* Go 1.11+
* Android SDK
- Build Tools 27+
- Android NDK r16+
### BUILD
......@@ -26,17 +25,18 @@ but probably painful. Further contributions regarding building on Windows are al
* Set environment variable `ANDROID_HOME` to `/path/to/android-sdk`
* (optional) Set environment variable `ANDROID_NDK_HOME` to `/path/to/android-ndk` (default: `$ANDROID_HOME/ndk-bundle`)
* Set environment variable `GOROOT_BOOTSTRAP` to `/path/to/go`
* Clone the repo using `git clone --recurse-submodules <repo>` or update submodules using `git submodule update --init --recursive`
* Build it using Android Studio or gradle script
### TRANSLATE
### BUILD WITH DOCKER
Translators can go to [POEditor](https://poeditor.com/join/project/u5VHO9vhSf) to help translate shadowsocks-android. Guidelines:
```bash
mkdir build
sudo chown 3434:3434 build
docker run --rm -v ${PWD}/build:/build shadowsocks/shadowsocks-android:circleci bash -c "cd /build; git clone https://github.com/shadowsocks/shadowsocks-android; cd shadowsocks-android; git submodule update --init --recursive; ./gradlew assembleDebug"
```
* It's okay to leave some strings untranslated if you think it should use the same string as English (US).
* `faq_url` should not be changed. If you'd like to translate FAQ, submit a pull request with the translated [`faq.md`](https://github.com/shadowsocks/shadowsocks-android/blob/master/.github/faq.md) (it should be named properly, e.g. `.github/faq.zh-CN.md`). Administrators will take care of the rest.
* Do not add/edit/remove comments.
### [TRANSLATE](https://discourse.shadowsocks.org/t/poeditor-translation-main-thread/30)
## OPEN SOURCE LICENSES
......
......@@ -4,27 +4,32 @@ apply plugin: 'com.github.ben-manes.versions'
buildscript {
ext {
kotlinVersion = '1.2.31'
kotlinVersion = '1.3.11'
minSdkVersion = 21
sdkVersion = 27
buildToolsVersion = '27.0.3'
supportLibraryVersion = '27.1.0'
takisoftFixVersion = '27.1.0.0'
playServicesVersion = '12.0.1'
sdkVersion = 28
compileSdkVersion = 28
buildToolsVersion = '28.0.3'
roomVersion = '2.0.0'
workVersion = '1.0.0-beta01'
preferencexVersion = '1.0.0'
junitVersion = '4.12'
androidTestVersion = '1.0.1'
androidEspressoVersion = '3.0.1'
androidTestVersion = '1.1.1'
androidEspressoVersion = '3.1.1'
}
repositories {
google()
jcenter()
maven {
url 'https://maven.fabric.io/public'
}
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.0'
classpath 'com.github.ben-manes:gradle-versions-plugin:0.17.0'
classpath 'com.google.gms:google-services:3.2.1'
classpath 'com.android.tools.build:gradle:3.2.1'
classpath 'com.github.ben-manes:gradle-versions-plugin:0.20.0'
classpath 'com.google.gms:google-services:4.2.0'
classpath 'io.fabric.tools:gradle:1.27.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
}
}
......
import org.apache.tools.ant.taskdefs.condition.Os
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
android {
buildToolsVersion rootProject.buildToolsVersion
compileSdkVersion rootProject.sdkVersion
compileSdkVersion rootProject.compileSdkVersion
defaultConfig {
minSdkVersion rootProject.minSdkVersion
......@@ -12,7 +14,7 @@ android {
versionCode 1
versionName "1.0.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
externalNativeBuild {
ndkBuild {
......@@ -20,6 +22,11 @@ android {
arguments "-j${Runtime.runtime.availableProcessors()}"
}
}
javaCompileOptions {
annotationProcessorOptions {
arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
}
}
}
buildTypes {
......@@ -56,15 +63,26 @@ task goClean(type: Exec) {
}
tasks.whenTaskAdded { task ->
if ((task.name == 'generateJsonModelDebug' ||
task.name == 'generateJsonModelRelease')) {
task.dependsOn(goBuild)
if ((task.name == 'generateJsonModelDebug' || task.name == 'generateJsonModelRelease')) {
task.dependsOn(goBuild)
}
}
dependencies {
api project(':plugin')
api "android.arch.work:work-runtime-ktx:$workVersion"
api "androidx.preference:preference:1.0.0"
api "androidx.room:room-runtime:$roomVersion"
api 'com.crashlytics.sdk.android:crashlytics:2.9.7'
api 'com.google.firebase:firebase-config:16.1.2'
api 'com.google.firebase:firebase-core:16.0.6'
api 'com.squareup.okhttp3:okhttp:3.12.0'
api "com.takisoft.preferencex:preferencex:$preferencexVersion"
api 'eu.chainfire:libsuperuser:1.0.0.201811281328'
kapt "androidx.room:room-compiler:$roomVersion"
testImplementation "androidx.room:room-testing:$roomVersion"
testImplementation "junit:junit:$junitVersion"
androidTestImplementation "com.android.support.test:runner:$androidTestVersion"
androidTestImplementation "com.android.support.test.espresso:espresso-core:$androidEspressoVersion"
androidTestImplementation "android.arch.work:work-testing:$workVersion"
androidTestImplementation "androidx.test:runner:$androidTestVersion"
androidTestImplementation "androidx.test.espresso:espresso-core:$androidEspressoVersion"
}
<?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="MissingDefaultResource" severity="warning" />
<issue id="MissingTranslation" severity="informational" />
</lint>
{
"formatVersion": 1,
"database": {
"version": 26,
"identityHash": "a69accf94393d71abdb60873d4ea74a2",
"entities": [
{
"tableName": "Profile",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT, `host` TEXT NOT NULL, `remotePort` INTEGER NOT NULL, `password` TEXT NOT NULL, `method` TEXT NOT NULL, `route` TEXT NOT NULL, `remoteDns` TEXT NOT NULL, `proxyApps` INTEGER NOT NULL, `bypass` INTEGER NOT NULL, `udpdns` INTEGER NOT NULL, `ipv6` INTEGER NOT NULL, `individual` TEXT NOT NULL, `tx` INTEGER NOT NULL, `rx` INTEGER NOT NULL, `userOrder` INTEGER NOT NULL, `plugin` TEXT)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "host",
"columnName": "host",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "remotePort",
"columnName": "remotePort",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "password",
"columnName": "password",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "method",
"columnName": "method",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "route",
"columnName": "route",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "remoteDns",
"columnName": "remoteDns",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "proxyApps",
"columnName": "proxyApps",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "bypass",
"columnName": "bypass",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "udpdns",
"columnName": "udpdns",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ipv6",
"columnName": "ipv6",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "individual",
"columnName": "individual",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "tx",
"columnName": "tx",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "rx",
"columnName": "rx",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "userOrder",
"columnName": "userOrder",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "plugin",
"columnName": "plugin",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "KeyValuePair",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `valueType` INTEGER NOT NULL, `value` BLOB NOT NULL, PRIMARY KEY(`key`))",
"fields": [
{
"fieldPath": "key",
"columnName": "key",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "valueType",
"columnName": "valueType",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "value",
"columnName": "value",
"affinity": "BLOB",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"key"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
}
],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"a69accf94393d71abdb60873d4ea74a2\")"
]
}
}
\ No newline at end of file
{
"formatVersion": 1,
"database": {
"version": 3,
"identityHash": "f1aab1fb633378621635c344dbc8ac7b",
"entities": [
{
"tableName": "KeyValuePair",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `valueType` INTEGER NOT NULL, `value` BLOB NOT NULL, PRIMARY KEY(`key`))",
"fields": [
{
"fieldPath": "key",
"columnName": "key",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "valueType",
"columnName": "valueType",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "value",
"columnName": "value",
"affinity": "BLOB",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"key"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
}
],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"f1aab1fb633378621635c344dbc8ac7b\")"
]
}
}
\ No newline at end of file
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.core.test", appContext.getPackageName());
}
}
......@@ -34,6 +34,6 @@ class AclTest {
@Test
fun parse() {
Assert.assertEquals(INPUT1, Acl().fromReader(INPUT1.reader()).toString());
Assert.assertEquals(INPUT1, Acl().fromReader(INPUT1.reader()).toString())
}
}
......@@ -20,17 +20,17 @@
package com.github.shadowsocks.database
import android.net.Uri
import androidx.core.net.toUri
import org.junit.Assert
import org.junit.Test
class ProfileTest {
@Test
fun parsing() {
val results = Profile.findAll("garble ss://YmYtY2ZiOnRlc3RAMTkyLjE2OC4xMDAuMTo4ODg4#example-server garble")
val results = Profile.findAllUrls("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"),
Assert.assertEquals("ss://YmYtY2ZiOnRlc3Q@192.168.100.1:8888#example-server".toUri(),
results.single().toUri())
}
}
package com.github.shadowsocks.utils
import java.net.InetAddress
import org.junit.Assert
import org.junit.Test
import java.net.InetAddress
class SubnetTest {
@Test
......
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.github.shadowsocks.core"/>
xmlns:tools="http://schemas.android.com/tools"
package="com.github.shadowsocks.core"
android:installLocation="internalOnly">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-feature android:name="android.software.leanback"
android:required="false"/>
<application
android:icon="@mipmap/ic_launcher"
android:allowBackup="true"
android:backupAgent="com.github.shadowsocks.ConfigBackupHelper"
android:fullBackupContent="@xml/backup_descriptor"
android:fullBackupOnly="true"
android:label="@string/app_name"
android:supportsRtl="true"
android:networkSecurityConfig="@xml/network_security_config"
android:banner="@mipmap/banner">
<meta-data android:name="android.webkit.WebView.EnableSafeBrowsing"
android:value="true" />
<meta-data android:name="com.google.android.backup.api_key"
android:value="AEdPqrEAAAAI_zVxZthz2HDuz9toTvkYvL0L5GA-OjeUIfBeXg"/>
<service
android:name="com.github.shadowsocks.bg.VpnService"
android:process=":bg"
android:directBootAware="true"
android:label="@string/app_name"
android:permission="android.permission.BIND_VPN_SERVICE"
android:exported="false">
<intent-filter>
<action android:name="android.net.VpnService"/>
</intent-filter>
</service>
<service
android:name="com.github.shadowsocks.bg.TransproxyService"
android:process=":bg"
android:directBootAware="true"
android:exported="false">
</service>
<service
android:name="com.github.shadowsocks.bg.ProxyService"
android:process=":bg"
android:directBootAware="true"
android:exported="false">
</service>
<activity
android:name="com.github.shadowsocks.VpnRequestActivity"
android:theme="@style/Theme.AppCompat.Translucent"
android:excludeFromRecents="true"
android:taskAffinity=""
android:launchMode="singleTask"/>
<receiver android:name="com.github.shadowsocks.BootReceiver"
android:process=":bg"
android:directBootAware="true"
android:enabled="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
</intent-filter>
</receiver>
<provider android:name="com.crashlytics.android.CrashlyticsInitProvider"
tools:node="remove"/>
<!-- https://android.googlesource.com/platform/frameworks/support/+/androidx-master-dev/work/workmanager/src/main/AndroidManifest.xml -->
<provider android:name="androidx.work.impl.WorkManagerInitializer"
tools:node="remove"/>
<service android:name="androidx.work.impl.background.systemalarm.SystemAlarmService"
android:process=":bg"
android:directBootAware="true"
tools:replace="android:directBootAware"/>
<service android:name="androidx.work.impl.background.systemjob.SystemJobService"
android:process=":bg"
android:directBootAware="true"
tools:replace="android:directBootAware"/>
<receiver android:name="androidx.work.impl.utils.ForceStopRunnable$BroadcastReceiver"
android:process=":bg"
android:directBootAware="true"
tools:replace="android:directBootAware"/>
<receiver android:name="androidx.work.impl.background.systemalarm.ConstraintProxy$BatteryChargingProxy"
android:process=":bg"
android:directBootAware="true"
tools:replace="android:directBootAware"/>
<receiver android:name="androidx.work.impl.background.systemalarm.ConstraintProxy$BatteryNotLowProxy"
android:process=":bg"
android:directBootAware="true"
tools:replace="android:directBootAware"/>
<receiver android:name="androidx.work.impl.background.systemalarm.ConstraintProxy$StorageNotLowProxy"
android:process=":bg"
android:directBootAware="true"
tools:replace="android:directBootAware"/>
<receiver android:name="androidx.work.impl.background.systemalarm.ConstraintProxy$NetworkStateProxy"
android:process=":bg"
android:directBootAware="true"
tools:replace="android:directBootAware"/>
<receiver android:name="androidx.work.impl.background.systemalarm.RescheduleReceiver"
android:process=":bg"
android:directBootAware="true"
tools:replace="android:directBootAware"/>
<receiver android:name="androidx.work.impl.background.systemalarm.ConstraintProxyUpdateReceiver"
android:process=":bg"
android:directBootAware="true"
tools:replace="android:directBootAware"/>
</application>
</manifest>
......@@ -2,7 +2,7 @@ package com.github.shadowsocks.aidl;
interface IShadowsocksServiceCallback {
oneway void stateChanged(int state, String profileName, String msg);
oneway void trafficUpdated(int profileId, long txRate, long rxRate, long txTotal, long rxTotal);
oneway void trafficUpdated(long profileId, long txRate, long rxRate, long txTotal, long rxTotal);
// Traffic data has persisted to database, listener should refetch their data from database
oneway void trafficPersisted(int profileId);
oneway void trafficPersisted(long profileId);
}
......@@ -48,6 +48,14 @@
1.192.0.0/13
1.202.0.0/15
1.204.0.0/14
8.128.0.0/10
8.208.32.0/19
8.208.64.0/18
8.208.128.0/17
8.209.0.0/16
8.210.0.0/15
8.212.0.0/14
8.216.0.0/13
14.0.0.0/21
14.0.12.0/22
14.1.0.0/22
......@@ -64,7 +72,7 @@
14.130.0.0/15
14.134.0.0/15
14.144.0.0/12
14.192.56.0/21
14.192.60.0/22
14.192.76.0/22
14.196.0.0/15
14.204.0.0/15
......@@ -154,6 +162,7 @@
39.96.0.0/13
39.104.0.0/14
39.108.0.0/16
39.109.120.0/23
39.128.0.0/10
40.0.176.0/20
40.0.247.0/24
......@@ -269,7 +278,6 @@
43.225.140.0/22
43.225.172.0/22
43.225.180.0/22
43.225.184.0/22
43.225.208.0/22
43.225.216.0/21
43.225.224.0/20
......@@ -548,7 +556,6 @@
45.113.52.0/22
45.113.56.0/22
45.113.72.0/22
45.113.108.0/22
45.113.144.0/21
45.113.168.0/22
45.113.176.0/22
......@@ -687,7 +694,6 @@
45.126.212.0/22
45.126.220.0/22
45.127.8.0/21
45.127.36.0/22
45.127.96.0/22
45.127.116.0/22
45.127.124.0/22
......@@ -743,6 +749,7 @@
45.251.160.0/19
45.251.192.0/19
45.251.224.0/22
45.251.240.0/22
45.252.0.0/19
45.252.32.0/20
45.252.48.0/22
......@@ -788,10 +795,10 @@
45.255.224.0/20
45.255.240.0/21
45.255.248.0/22
46.61.179.170/31
46.248.24.0/23
47.92.0.0/14
47.96.0.0/11
47.252.128.0/17
49.4.0.0/14
49.51.0.0/16
49.52.0.0/14
......@@ -811,6 +818,8 @@
49.246.224.0/19
52.80.0.0/14
52.94.249.0/27
52.95.216.104/30
52.130.0.0/15
54.222.0.0/15
54.231.208.0/20
54.240.224.0/24
......@@ -879,6 +888,7 @@
61.4.80.0/20
61.4.176.0/20
61.8.160.0/20
61.14.4.0/22
61.14.212.0/22
61.14.216.0/21
61.14.240.0/21
......@@ -890,14 +900,26 @@
61.48.0.0/13
61.87.192.0/18
61.128.0.0/10
61.213.145.106/32
61.232.0.0/14
61.236.0.0/15
61.240.0.0/14
62.234.0.0/16
64.85.27.0/24
65.55.60.184/30
68.79.0.0/18
69.230.192.0/18
69.231.128.0/18
69.234.192.0/18
69.235.128.0/18
71.131.192.0/18
71.132.0.0/18
71.136.64.0/18
71.137.0.0/18
72.163.248.0/22
81.68.0.0/14
82.156.0.0/15
87.254.207.0/24
91.234.36.0/24
93.183.14.0/24
93.183.18.0/24
94.191.0.0/17
......@@ -1052,7 +1074,6 @@
103.14.32.0/22
103.14.84.0/22
103.14.100.0/22
103.14.112.0/22
103.14.132.0/22
103.14.136.0/22
103.14.156.0/22
......@@ -1418,7 +1439,6 @@
103.57.136.0/22
103.57.196.0/22
103.58.24.0/22
103.58.182.0/23
103.59.76.0/22
103.59.100.0/22
103.59.112.0/20
......@@ -1621,7 +1641,6 @@
103.80.184.0/22
103.80.192.0/22
103.80.200.0/22
103.80.212.0/22
103.80.232.0/22
103.81.4.0/22
103.81.8.0/22
......@@ -1702,7 +1721,6 @@
103.88.64.0/22
103.88.72.0/22
103.88.96.0/21
103.88.139.0/24
103.88.152.0/23
103.88.164.0/22
103.88.176.0/22
......@@ -1868,6 +1886,7 @@
103.101.184.0/22
103.102.76.0/22
103.102.80.0/22
103.102.163.0/24
103.102.168.0/21
103.102.180.0/22
103.102.184.0/21
......@@ -1919,6 +1938,145 @@
103.106.196.0/22
103.106.202.0/23
103.106.212.0/22
103.106.244.0/22
103.106.252.0/22
103.107.0.0/22
103.107.8.0/24
103.107.28.0/22
103.107.32.0/22
103.107.44.0/22
103.107.72.0/22
103.107.108.0/22
103.107.164.0/22
103.107.168.0/22
103.107.188.0/22
103.107.192.0/22
103.107.208.0/20
103.108.52.0/22
103.108.64.0/22
103.108.160.0/21
103.108.184.0/23
103.108.188.0/23
103.108.192.0/21
103.108.208.0/21
103.108.224.0/22
103.108.244.0/22
103.108.251.0/24
103.109.20.0/22
103.109.48.0/22
103.109.88.0/22
103.109.106.0/23
103.109.248.0/22
103.110.32.0/22
103.110.80.0/23
103.110.92.0/22
103.110.100.0/22
103.110.116.0/22
103.110.127.0/24
103.110.128.0/23
103.110.131.0/24
103.110.132.0/22
103.110.136.0/22
103.110.152.0/21
103.110.188.0/22
103.110.204.0/22
103.111.38.0/23
103.111.64.0/22
103.111.172.0/22
103.111.252.0/22
103.112.28.0/22
103.112.68.0/22
103.112.72.0/22
103.112.88.0/21
103.112.96.0/22
103.112.108.0/22
103.112.112.0/21
103.112.140.0/22
103.112.172.0/22
103.112.184.0/22
103.112.208.0/22
103.113.4.0/22
103.113.92.0/22
103.113.144.0/22
103.113.220.0/22
103.113.232.0/21
103.114.4.0/22
103.114.28.0/22
103.114.68.0/22
103.114.72.0/22
103.114.100.0/22
103.114.132.0/22
103.114.148.0/22
103.114.156.0/22
103.114.176.0/22
103.114.212.0/22
103.114.236.0/22
103.114.240.0/22
103.115.16.0/22
103.115.40.0/21
103.115.48.0/20
103.115.64.0/21
103.115.92.0/22
103.115.120.0/22
103.115.148.0/22
103.115.204.0/23
103.115.248.0/22
103.116.20.0/22
103.116.40.0/22
103.116.64.0/22
103.116.72.0/21
103.116.92.0/22
103.116.120.0/22
103.116.128.0/22
103.116.132.0/23
103.116.148.0/22
103.116.184.0/22
103.116.206.0/23
103.116.220.0/22
103.116.224.0/21
103.117.16.0/22
103.117.72.0/22
103.117.88.0/22
103.117.132.0/22
103.117.136.0/22
103.117.188.0/22
103.117.220.0/22
103.118.19.0/24
103.118.36.0/22
103.118.52.0/22
103.118.56.0/21
103.118.64.0/21
103.118.72.0/22
103.118.88.0/22
103.118.173.0/24
103.118.192.0/19
103.118.240.0/20
103.119.0.0/22
103.119.12.0/22
103.119.16.0/22
103.119.28.0/22
103.119.44.0/22
103.119.104.0/22
103.119.115.0/24
103.119.156.0/22
103.119.180.0/22
103.119.200.0/22
103.119.224.0/22
103.120.52.0/22
103.120.72.0/22
103.120.76.0/24
103.120.88.0/22
103.120.96.0/21
103.120.140.0/22
103.120.196.0/22
103.120.224.0/22
103.121.52.0/22
103.121.92.0/22
103.121.160.0/21
103.121.250.0/24
103.121.252.0/22
103.129.52.0/22
103.129.148.0/22
103.192.0.0/19
103.192.48.0/21
103.192.56.0/22
......@@ -2091,7 +2249,7 @@
103.211.168.0/22
103.211.192.0/22
103.211.220.0/22
103.211.224.0/21
103.211.224.0/22
103.211.248.0/22
103.212.0.0/20
103.212.32.0/22
......@@ -2120,7 +2278,6 @@
103.214.84.0/22
103.214.168.0/22
103.214.212.0/22
103.214.224.0/22
103.214.240.0/21
103.215.28.0/22
103.215.32.0/21
......@@ -2207,7 +2364,6 @@
103.223.188.0/22
103.223.192.0/18
103.224.0.0/22
103.224.16.0/22
103.224.40.0/21
103.224.60.0/22
103.224.80.0/22
......@@ -2491,8 +2647,6 @@
106.108.0.0/14
106.112.0.0/12
106.224.0.0/12
107.153.91.0/24
107.153.92.0/23
109.71.4.0/24
109.244.0.0/16
110.6.0.0/15
......@@ -2615,6 +2769,7 @@
113.136.0.0/13
113.194.0.0/15
113.197.100.0/22
113.197.104.0/22
113.200.0.0/15
113.202.0.0/16
113.204.0.0/14
......@@ -2730,7 +2885,7 @@
116.207.0.0/16
116.208.0.0/14
116.212.160.0/20
116.213.40.0/21
116.213.44.0/22
116.213.64.0/18
116.213.128.0/17
116.214.32.0/19
......@@ -2774,7 +2929,6 @@
117.103.72.0/21
117.103.128.0/20
117.104.168.0/21
117.104.188.0/24
117.106.0.0/15
117.112.0.0/13
117.120.64.0/18
......@@ -3076,7 +3230,6 @@
124.150.137.0/24
124.151.0.0/16
124.152.0.0/16
124.156.0.0/16
124.160.0.0/13
124.172.0.0/14
124.192.0.0/15
......@@ -3116,7 +3269,11 @@
129.204.0.0/16
129.211.0.0/16
129.223.254.0/24
130.214.218.0/23
131.228.96.0/24
131.253.12.0/29
131.253.12.80/28
131.253.12.240/29
132.232.0.0/16
132.237.134.0/24
132.237.150.0/24
......@@ -3157,6 +3314,7 @@
140.75.0.0/16
140.101.208.0/24
140.143.0.0/16
140.179.0.0/16
140.205.0.0/16
140.206.0.0/15
140.210.0.0/16
......@@ -3176,11 +3334,9 @@
144.12.0.0/16
144.36.146.0/23
144.48.8.0/21
144.48.56.0/22
144.48.64.0/22
144.48.88.0/22
144.48.156.0/22
144.48.172.0/22
144.48.180.0/22
144.48.184.0/22
144.48.204.0/22
......@@ -3195,7 +3351,7 @@
146.56.192.0/18
146.196.56.0/22
146.196.68.0/22
146.196.72.0/21
146.196.72.0/22
146.196.92.0/22
146.196.112.0/21
146.196.124.0/22
......@@ -3203,9 +3359,10 @@
146.222.79.0/24
146.222.81.0/24
146.222.94.0/24
147.243.13.32/27
147.243.13.64/27
148.70.0.0/16
150.0.0.0/16
150.109.0.0/16
150.115.0.0/16
150.121.0.0/16
150.122.0.0/16
......@@ -3214,6 +3371,7 @@
150.129.216.0/22
150.129.252.0/22
150.138.0.0/15
150.158.0.0/16
150.223.0.0/16
150.242.0.0/21
150.242.8.0/22
......@@ -3237,6 +3395,10 @@
150.242.240.0/21
150.242.248.0/22
150.255.0.0/16
152.32.136.0/21
152.32.144.0/20
152.32.160.0/19
152.32.192.0/18
152.104.128.0/17
152.136.0.0/16
153.0.0.0/16
......@@ -3274,6 +3436,7 @@
157.148.0.0/16
157.156.0.0/16
157.255.0.0/16
159.75.0.0/16
159.153.120.0/22
159.226.0.0/16
160.19.208.0/21
......@@ -3295,8 +3458,9 @@
161.163.176.0/24
161.163.178.0/23
161.163.180.0/22
161.189.0.0/16
161.207.0.0/16
162.62.0.0/16
162.14.0.0/16
162.105.0.0/16
163.0.0.0/16
163.47.4.0/22
......@@ -3332,7 +3496,6 @@
168.159.158.0/24
168.160.0.0/16
168.230.0.0/24
170.106.0.0/16
170.179.0.0/16
170.225.224.0/23
170.252.152.0/21
......@@ -3368,6 +3531,7 @@
175.176.156.0/22
175.176.176.0/22
175.176.188.0/22
175.176.192.0/22
175.178.0.0/16
175.184.128.0/18
175.185.0.0/16
......@@ -3466,6 +3630,7 @@
183.184.0.0/13
183.192.0.0/10
185.109.236.0/24
188.65.16.0/22
188.131.128.0/17
192.11.23.0/24
192.11.26.0/24
......@@ -3489,12 +3654,10 @@
192.140.208.0/21
192.144.128.0/17
192.163.11.0/24
192.200.24.0/24
192.232.97.0/24
193.20.64.0/22
193.112.0.0/16
194.138.202.0/23
195.78.82.0/23
198.175.100.0/22
198.208.17.0/24
199.7.72.0/24
......@@ -3796,7 +3959,11 @@
202.94.74.0/24
202.94.81.0/24
202.94.92.0/22
202.95.0.0/19
202.95.1.0/24
202.95.2.0/23
202.95.4.0/22
202.95.8.0/21
202.95.16.0/20
202.95.240.0/21
202.95.252.0/22
202.96.0.0/12
......@@ -3893,7 +4060,6 @@
202.164.96.0/19
202.165.96.0/21
202.165.104.0/22
202.165.110.0/23
202.165.176.0/20
202.165.208.0/20
202.165.239.0/24
......@@ -4887,7 +5053,7 @@
203.189.6.0/23
203.189.112.0/22
203.189.192.0/19
203.189.232.0/21
203.189.232.0/22
203.189.240.0/22
203.190.96.0/20
203.190.249.0/24
......@@ -5059,7 +5225,6 @@
223.116.0.0/15
223.120.0.0/13
223.128.0.0/15
223.130.8.0/22
223.144.0.0/12
223.160.0.0/14
223.166.0.0/15
......
......@@ -6,15 +6,14 @@
[bypass_all]
[proxy_list]
# Telegram IPs$
91.108.56.0/23
91.108.56.0/22
91.108.4.0/22
149.154.172.0/22
149.154.168.0/22
149.154.164.0/22
91.108.8.0/21
91.108.16.0/21
91.108.36.0/22
91.108.56.0/22
109.239.140.0/24
149.154.160.0/20
14.102.250.18
14.102.250.19
174.142.105.153
......@@ -561,9 +560,7 @@
(^|\.)blogs\.libraryinformationtechnology\.com$
(^|\.)blogs\.tampabay\.com$
(^|\.)blogs\.yahoo\.co\.jp$
(^|\.)blogspot\.com$
(^|\.)blogspot\.hk$
(^|\.)blogspot\.jp$
(^|\.)blogspot(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?){1,2}$
(^|\.)blogtd\.net$
(^|\.)blogtd\.org$
(^|\.)bloodshed\.net$
......@@ -1802,61 +1799,8 @@
(^|\.)goodtv\.com\.tw$
(^|\.)goodtv\.tv$
(^|\.)goofind\.com$
(^|\.)google\.ae$
(^|\.)google\.am$
(^|\.)google\.as$
(^|\.)google\.at$
(^|\.)google\.az$
(^|\.)google\.ba$
(^|\.)google\.be$
(^|\.)google\.bg$
(^|\.)google\.ca$
(^|\.)google\.calstate\.edu$
(^|\.)google\.cd$
(^|\.)google\.ci$
(^|\.)google\.co\.id$
(^|\.)google\.co\.jp$
(^|\.)google\.co\.kr$
(^|\.)google\.co\.ma$
(^|\.)google\.co\.uk$
(^|\.)google\.com$
(^|\.)google\.de$
(^|\.)google\.dj$
(^|\.)google\.dk$
(^|\.)google\.es$
(^|\.)google\.fi$
(^|\.)google\.fm$
(^|\.)google\.fr$
(^|\.)google\.gg$
(^|\.)google\.gl$
(^|\.)google\.gr$
(^|\.)google\.ie$
(^|\.)google\.is$
(^|\.)google\.it$
(^|\.)google\.jo$
(^|\.)google\.kz$
(^|\.)google\.lv$
(^|\.)google\.mn$
(^|\.)google\.ms$
(^|\.)google\.nl$
(^|\.)google\.no$
(^|\.)google\.nu$
(^|\.)google\.ro$
(^|\.)google\.ru$
(^|\.)google\.rw$
(^|\.)google\.sc$
(^|\.)google\.sh$
(^|\.)google\.sk$
(^|\.)google\.sm$
(^|\.)google\.sn$
(^|\.)google\.tk$
(^|\.)google\.tm$
(^|\.)google\.to$
(^|\.)google\.tt$
(^|\.)google\.vu$
(^|\.)google\.ws$
(^|\.)googleapis\.cn$
(^|\.)googleapis\.com$
(^|\.)google(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?){1,2}$
(^|\.)googleapis(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?){1,2}$
(^|\.)googleapps\.com$
(^|\.)googlearth\.com$
(^|\.)googleartproject\.com$
......
0.0.0.0/8
10.0.0.0/8
100.64.0.0/10
127.0.0.0/8
169.254.0.0/16
172.16.0.0/12
192.0.0.0/29
192.0.2.0/24
192.88.99.0/24
192.168.0.0/16
198.18.0.0/15
198.51.100.0/24
203.0.113.0/24
224.0.0.0/3
1.0.1.0/24
1.0.2.0/23
1.0.8.0/21
......@@ -59,6 +45,8 @@
1.192.0.0/13
1.202.0.0/15
1.204.0.0/14
8.128.0.0/10
8.208.0.0/12
14.0.0.0/21
14.0.12.0/22
14.1.0.0/22
......@@ -165,6 +153,7 @@
39.96.0.0/13
39.104.0.0/14
39.108.0.0/16
39.109.120.0/23
39.128.0.0/10
40.0.176.0/20
40.0.247.0/24
......@@ -280,7 +269,6 @@
43.225.140.0/22
43.225.172.0/22
43.225.180.0/22
43.225.184.0/22
43.225.208.0/22
43.225.216.0/21
43.225.224.0/20
......@@ -559,7 +547,6 @@
45.113.52.0/22
45.113.56.0/22
45.113.72.0/22
45.113.108.0/22
45.113.144.0/21
45.113.168.0/22
45.113.176.0/22
......@@ -698,7 +685,6 @@
45.126.212.0/22
45.126.220.0/22
45.127.8.0/21
45.127.36.0/22
45.127.96.0/22
45.127.116.0/22
45.127.124.0/22
......@@ -799,10 +785,10 @@
45.255.224.0/20
45.255.240.0/21
45.255.248.0/22
46.61.179.170/31
46.248.24.0/23
47.92.0.0/14
47.96.0.0/11
47.252.128.0/17
49.4.0.0/14
49.51.0.0/16
49.52.0.0/14
......@@ -822,6 +808,8 @@
49.246.224.0/19
52.80.0.0/14
52.94.249.0/27
52.95.216.104/30
52.130.0.0/15
54.222.0.0/15
54.231.208.0/20
54.240.224.0/24
......@@ -890,6 +878,7 @@
61.4.80.0/20
61.4.176.0/20
61.8.160.0/20
61.14.4.0/22
61.14.212.0/22
61.14.216.0/21
61.14.240.0/21
......@@ -901,14 +890,25 @@
61.48.0.0/13
61.87.192.0/18
61.128.0.0/10
61.213.145.106/32
61.232.0.0/14
61.236.0.0/15
61.240.0.0/14
62.234.0.0/16
64.85.27.0/24
68.79.0.0/18
69.230.192.0/18
69.231.128.0/18
69.234.192.0/18
69.235.128.0/18
71.131.192.0/18
71.132.0.0/18
71.136.64.0/18
71.137.0.0/18
72.163.248.0/22
81.68.0.0/14
82.156.0.0/15
87.254.207.0/24
91.234.36.0/24
93.183.14.0/24
93.183.18.0/24
94.191.0.0/17
......@@ -1063,7 +1063,6 @@
103.14.32.0/22
103.14.84.0/22
103.14.100.0/22
103.14.112.0/22
103.14.132.0/22
103.14.136.0/22
103.14.156.0/22
......@@ -1632,7 +1631,6 @@
103.80.184.0/22
103.80.192.0/22
103.80.200.0/22
103.80.212.0/22
103.80.232.0/22
103.81.4.0/22
103.81.8.0/22
......@@ -1713,7 +1711,6 @@
103.88.64.0/22
103.88.72.0/22
103.88.96.0/21
103.88.139.0/24
103.88.152.0/23
103.88.164.0/22
103.88.176.0/22
......@@ -1879,6 +1876,7 @@
103.101.184.0/22
103.102.76.0/22
103.102.80.0/22
103.102.163.0/24
103.102.168.0/21
103.102.180.0/22
103.102.184.0/21
......@@ -1930,6 +1928,128 @@
103.106.196.0/22
103.106.202.0/23
103.106.212.0/22
103.106.244.0/22
103.106.252.0/22
103.107.0.0/22
103.107.8.0/24
103.107.28.0/22
103.107.32.0/22
103.107.44.0/22
103.107.72.0/22
103.107.108.0/22
103.107.164.0/22
103.107.168.0/22
103.107.188.0/22
103.107.192.0/22
103.107.208.0/20
103.108.52.0/22
103.108.64.0/22
103.108.160.0/21
103.108.184.0/23
103.108.188.0/23
103.108.192.0/21
103.108.208.0/21
103.108.224.0/22
103.108.244.0/22
103.108.251.0/24
103.109.20.0/22
103.109.48.0/22
103.109.88.0/22
103.109.106.0/23
103.109.248.0/22
103.110.32.0/22
103.110.80.0/23
103.110.92.0/22
103.110.100.0/22
103.110.116.0/22
103.110.127.0/24
103.110.128.0/23
103.110.131.0/24
103.110.132.0/22
103.110.136.0/22
103.110.152.0/21
103.110.188.0/22
103.110.204.0/22
103.111.38.0/23
103.111.64.0/22
103.111.172.0/22
103.111.252.0/22
103.112.28.0/22
103.112.68.0/22
103.112.72.0/22
103.112.88.0/21
103.112.96.0/22
103.112.108.0/22
103.112.112.0/21
103.112.140.0/22
103.112.172.0/22
103.112.184.0/22
103.112.208.0/22
103.113.4.0/22
103.113.92.0/22
103.113.144.0/22
103.113.220.0/22
103.113.232.0/21
103.114.4.0/22
103.114.28.0/22
103.114.68.0/22
103.114.72.0/22
103.114.100.0/22
103.114.132.0/22
103.114.148.0/22
103.114.156.0/22
103.114.176.0/22
103.114.212.0/22
103.114.236.0/22
103.114.240.0/22
103.115.16.0/22
103.115.40.0/21
103.115.48.0/20
103.115.64.0/21
103.115.92.0/22
103.115.120.0/22
103.115.148.0/22
103.115.204.0/23
103.115.248.0/22
103.116.20.0/22
103.116.40.0/22
103.116.64.0/22
103.116.72.0/21
103.116.92.0/22
103.116.120.0/22
103.116.128.0/22
103.116.132.0/23
103.116.148.0/22
103.116.184.0/22
103.116.206.0/23
103.116.220.0/22
103.116.224.0/21
103.117.16.0/22
103.117.72.0/22
103.117.88.0/22
103.117.132.0/22
103.117.136.0/22
103.117.188.0/22
103.117.220.0/22
103.118.19.0/24
103.118.36.0/22
103.118.52.0/22
103.118.56.0/21
103.118.64.0/21
103.118.72.0/22
103.118.88.0/22
103.118.173.0/24
103.118.192.0/19
103.118.240.0/20
103.119.0.0/22
103.119.12.0/22
103.119.16.0/22
103.119.28.0/22
103.119.44.0/22
103.119.104.0/22
103.119.115.0/24
103.119.156.0/22
103.119.180.0/22
103.192.0.0/19
103.192.48.0/21
103.192.56.0/22
......@@ -2102,7 +2222,7 @@
103.211.168.0/22
103.211.192.0/22
103.211.220.0/22
103.211.224.0/21
103.211.224.0/22
103.211.248.0/22
103.212.0.0/20
103.212.32.0/22
......@@ -2131,7 +2251,6 @@
103.214.84.0/22
103.214.168.0/22
103.214.212.0/22
103.214.224.0/22
103.214.240.0/21
103.215.28.0/22
103.215.32.0/21
......@@ -2218,7 +2337,6 @@
103.223.188.0/22
103.223.192.0/18
103.224.0.0/22
103.224.16.0/22
103.224.40.0/21
103.224.60.0/22
103.224.80.0/22
......@@ -2741,7 +2859,7 @@
116.207.0.0/16
116.208.0.0/14
116.212.160.0/20
116.213.40.0/21
116.213.44.0/22
116.213.64.0/18
116.213.128.0/17
116.214.32.0/19
......@@ -2785,7 +2903,6 @@
117.103.72.0/21
117.103.128.0/20
117.104.168.0/21
117.104.188.0/24
117.106.0.0/15
117.112.0.0/13
117.120.64.0/18
......@@ -3127,6 +3244,8 @@
129.204.0.0/16
129.211.0.0/16
129.223.254.0/24
129.226.0.0/16
130.214.218.0/23
131.228.96.0/24
132.232.0.0/16
132.237.134.0/24
......@@ -3168,6 +3287,7 @@
140.75.0.0/16
140.101.208.0/24
140.143.0.0/16
140.179.0.0/16
140.205.0.0/16
140.206.0.0/15
140.210.0.0/16
......@@ -3187,11 +3307,9 @@
144.12.0.0/16
144.36.146.0/23
144.48.8.0/21
144.48.56.0/22
144.48.64.0/22
144.48.88.0/22
144.48.156.0/22
144.48.172.0/22
144.48.180.0/22
144.48.184.0/22
144.48.204.0/22
......@@ -3206,7 +3324,7 @@
146.56.192.0/18
146.196.56.0/22
146.196.68.0/22
146.196.72.0/21
146.196.72.0/22
146.196.92.0/22
146.196.112.0/21
146.196.124.0/22
......@@ -3216,7 +3334,6 @@
146.222.94.0/24
148.70.0.0/16
150.0.0.0/16
150.109.0.0/16
150.115.0.0/16
150.121.0.0/16
150.122.0.0/16
......@@ -3285,6 +3402,7 @@
157.148.0.0/16
157.156.0.0/16
157.255.0.0/16
159.75.0.0/16
159.153.120.0/22
159.226.0.0/16
160.19.208.0/21
......@@ -3306,8 +3424,16 @@
161.163.176.0/24
161.163.178.0/23
161.163.180.0/22
161.189.0.0/16
161.207.0.0/16
162.62.0.0/16
162.14.0.0/16
162.62.32.0/19
162.62.64.0/18
162.62.132.0/22
162.62.136.0/21
162.62.144.0/20
162.62.160.0/19
162.62.192.0/18
162.105.0.0/16
163.0.0.0/16
163.47.4.0/22
......@@ -3343,7 +3469,6 @@
168.159.158.0/24
168.160.0.0/16
168.230.0.0/24
170.106.0.0/16
170.179.0.0/16
170.225.224.0/23
170.252.152.0/21
......@@ -3379,6 +3504,7 @@
175.176.156.0/22
175.176.176.0/22
175.176.188.0/22
175.176.192.0/22
175.178.0.0/16
175.184.128.0/18
175.185.0.0/16
......@@ -3477,6 +3603,7 @@
183.184.0.0/13
183.192.0.0/10
185.109.236.0/24
185.252.218.0/23
188.131.128.0/17
192.11.23.0/24
192.11.26.0/24
......@@ -3500,12 +3627,11 @@
192.140.208.0/21
192.144.128.0/17
192.163.11.0/24
192.200.24.0/24
192.232.97.0/24
193.20.64.0/22
193.22.14.0/23
193.112.0.0/16
194.138.202.0/23
195.78.82.0/23
198.175.100.0/22
198.208.17.0/24
199.7.72.0/24
......@@ -3904,7 +4030,6 @@
202.164.96.0/19
202.165.96.0/21
202.165.104.0/22
202.165.110.0/23
202.165.176.0/20
202.165.208.0/20
202.165.239.0/24
......@@ -4898,7 +5023,7 @@
203.189.6.0/23
203.189.112.0/22
203.189.192.0/19
203.189.232.0/21
203.189.232.0/22
203.189.240.0/22
203.190.96.0/20
203.190.249.0/24
......@@ -5070,7 +5195,6 @@
223.116.0.0/15
223.120.0.0/13
223.128.0.0/15
223.130.8.0/22
223.144.0.0/12
223.160.0.0/14
223.166.0.0/15
......
......@@ -25,7 +25,7 @@ import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.Core.app
import com.github.shadowsocks.preference.DataStore
class BootReceiver : BroadcastReceiver() {
......@@ -45,6 +45,6 @@ class BootReceiver : BroadcastReceiver() {
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()
if (DataStore.directBootAware == locked) Core.startService()
}
}
/*******************************************************************************
* *
* Copyright (C) 2018 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2018 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
import android.app.Application
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.admin.DevicePolicyManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.os.UserManager
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService
import androidx.work.WorkManager
import com.crashlytics.android.Crashlytics
import com.github.shadowsocks.acl.Acl
import com.github.shadowsocks.bg.BaseService
import com.github.shadowsocks.core.R
import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.database.ProfileManager
import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.*
import com.google.firebase.FirebaseApp
import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.remoteconfig.FirebaseRemoteConfig
import io.fabric.sdk.android.Fabric
import java.io.File
import java.io.IOException
import kotlin.reflect.KClass
object Core {
const val TAG = "Core"
lateinit var app: Application
lateinit var configureIntent: (Context) -> PendingIntent
val handler by lazy { Handler(Looper.getMainLooper()) }
val packageInfo: PackageInfo by lazy { getPackageInfo(app.packageName) }
val deviceStorage by lazy { if (Build.VERSION.SDK_INT < 24) app else DeviceStorageApp(app) }
val remoteConfig: FirebaseRemoteConfig by lazy { FirebaseRemoteConfig.getInstance() }
val analytics: FirebaseAnalytics by lazy { FirebaseAnalytics.getInstance(deviceStorage) }
val directBootSupported by lazy {
Build.VERSION.SDK_INT >= 24 && app.getSystemService<DevicePolicyManager>()?.storageEncryptionStatus ==
DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER
}
val currentProfile: Profile? get() =
if (DataStore.directBootAware) DirectBoot.getDeviceProfile() else ProfileManager.getProfile(DataStore.profileId)
fun switchProfile(id: Long): Profile {
val result = ProfileManager.getProfile(id) ?: ProfileManager.createProfile()
DataStore.profileId = result.id
return result
}
fun init(app: Application, configureClass: KClass<out Any>) {
this.app = app
this.configureIntent = {
PendingIntent.getActivity(it, 0, Intent(it, configureClass.java)
.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT), 0)
}
if (Build.VERSION.SDK_INT >= 24) { // migrate old files
deviceStorage.moveDatabaseFrom(app, Key.DB_PUBLIC)
val old = Acl.getFile(Acl.CUSTOM_RULES, app)
if (old.canRead()) {
Acl.getFile(Acl.CUSTOM_RULES).writeText(old.readText())
old.delete()
}
}
Fabric.with(deviceStorage, Crashlytics()) // multiple processes needs manual set-up
FirebaseApp.initializeApp(deviceStorage)
remoteConfig.setDefaults(R.xml.default_configs)
remoteConfig.fetch().addOnCompleteListener {
if (it.isSuccessful) remoteConfig.activateFetched() else {
Log.e(TAG, "Failed to fetch config")
Crashlytics.logException(it.exception)
}
}
WorkManager.initialize(deviceStorage, androidx.work.Configuration.Builder().build())
// handle data restored/crash
if (Build.VERSION.SDK_INT >= 24 && DataStore.directBootAware &&
app.getSystemService<UserManager>()?.isUserUnlocked == true) DirectBoot.flushTrafficStats()
if (DataStore.tcpFastOpen) TcpFastOpen.enabledAsync(true)
if (DataStore.publicStore.getLong(Key.assetUpdateTime, -1) != packageInfo.lastUpdateTime) {
val assetManager = app.assets
for (dir in arrayOf("acl", "overture"))
try {
for (file in assetManager.list(dir)!!) assetManager.open("$dir/$file").use { input ->
File(deviceStorage.noBackupFilesDir, file).outputStream().use { output -> input.copyTo(output) }
}
} catch (e: IOException) {
printLog(e)
}
DataStore.publicStore.putLong(Key.assetUpdateTime, packageInfo.lastUpdateTime)
}
updateNotificationChannels()
}
fun updateNotificationChannels() {
if (Build.VERSION.SDK_INT >= 26) @RequiresApi(26) {
val nm = app.getSystemService<NotificationManager>()!!
nm.createNotificationChannels(listOf(
NotificationChannel("service-vpn", app.getText(R.string.service_vpn),
NotificationManager.IMPORTANCE_LOW),
NotificationChannel("service-proxy", app.getText(R.string.service_proxy),
NotificationManager.IMPORTANCE_LOW),
NotificationChannel("service-transproxy", app.getText(R.string.service_transproxy),
NotificationManager.IMPORTANCE_LOW)))
nm.deleteNotificationChannel("service-nat") // NAT mode is gone for good
}
}
fun getPackageInfo(packageName: String) = app.packageManager.getPackageInfo(packageName,
if (Build.VERSION.SDK_INT >= 28) PackageManager.GET_SIGNING_CERTIFICATES
else @Suppress("DEPRECATION") PackageManager.GET_SIGNATURES)!!
fun startService() = ContextCompat.startForegroundService(app, Intent(app, BaseService.serviceClass.java))
fun reloadService() = app.sendBroadcast(Intent(Action.RELOAD))
fun stopService() = app.sendBroadcast(Intent(Action.CLOSE))
fun listenForPackageChanges(onetime: Boolean = true, callback: () -> Unit): BroadcastReceiver {
val filter = IntentFilter(Intent.ACTION_PACKAGE_ADDED)
filter.addAction(Intent.ACTION_PACKAGE_REMOVED)
filter.addDataScheme("package")
val result = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) return
callback()
if (onetime) app.unregisterReceiver(this)
}
}
app.registerReceiver(result, filter)
return result
}
}
......@@ -38,39 +38,17 @@
package com.github.shadowsocks;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.system.ErrnoException;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class JniHelper {
static {
System.loadLibrary("jni-helper");
}
@Deprecated // Use Process.destroy() since API 24
public static void sigtermCompat(@NonNull Process process) throws Exception {
if (Build.VERSION.SDK_INT >= 24) throw new UnsupportedOperationException("Never call this method in OpenJDK!");
int errno = sigterm(process);
if (errno != 0) throw new ErrnoException("kill", errno);
}
@Deprecated // only implemented for before API 24
public static boolean waitForCompat(@NonNull Process process, long millis) throws Exception {
if (Build.VERSION.SDK_INT >= 24) throw new UnsupportedOperationException("Never call this method in OpenJDK!");
final Object mutex = getExitValueMutex(process);
synchronized (mutex) {
if (getExitValue(process) == null) mutex.wait(millis);
return getExitValue(process) != null;
}
}
public static native int sigkill(int pid);
private static native int sigterm(Process process);
private static native Integer getExitValue(Process process);
private static native Object getExitValueMutex(Process process);
public static native int sendFd(int fd, @NonNull String path);
public static native void close(int fd);
public static native void sendFd(int fd, @NonNull String path) throws ErrnoException;
@Nullable
public static native byte[] parseNumericAddress(@NonNull String str);
}
......@@ -20,10 +20,12 @@
package com.github.shadowsocks
import android.app.Fragment
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.DeadObjectException
import android.os.IBinder
import android.os.RemoteException
import com.github.shadowsocks.aidl.IShadowsocksService
......@@ -40,7 +42,7 @@ class ShadowsocksConnection(private val instance: Interface) : ServiceConnection
interface Interface : IBinder.DeathRecipient {
val serviceCallback: IShadowsocksServiceCallback? get() = null
val connection: ShadowsocksConnection
get() = connections.getOrPut(this, { ShadowsocksConnection(this) })
get() = connections.getOrPut(this) { ShadowsocksConnection(this) }
val listenForDeath get() = false
fun onServiceConnected(service: IShadowsocksService) { }
......@@ -56,13 +58,20 @@ class ShadowsocksConnection(private val instance: Interface) : ServiceConnection
private var connectionActive = false
private var callbackRegistered = false
private var binder: IBinder? = null
private val context by lazy {
when (instance) {
is Fragment -> instance.activity
else -> instance as Context
}
}
var listeningForBandwidth = false
set(value) {
val service = service
if (listeningForBandwidth != value && service != null && instance.serviceCallback != null)
if (value) service.startListeningForBandwidth(instance.serviceCallback)
else service.stopListeningForBandwidth(instance.serviceCallback)
if (value) service.startListeningForBandwidth(instance.serviceCallback) else try {
service.stopListeningForBandwidth(instance.serviceCallback)
} catch (_: DeadObjectException) { }
field = value
}
var service: IShadowsocksService? = null
......@@ -98,15 +107,15 @@ class ShadowsocksConnection(private val instance: Interface) : ServiceConnection
fun connect() {
if (connectionActive) return
connectionActive = true
val intent = Intent(instance as Context, BaseService.serviceClass.java).setAction(Action.SERVICE)
instance.bindService(intent, this, Context.BIND_AUTO_CREATE)
val intent = Intent(context, BaseService.serviceClass.java).setAction(Action.SERVICE)
context.bindService(intent, this, Context.BIND_AUTO_CREATE)
}
fun disconnect() {
unregisterCallback()
instance.onServiceDisconnected()
if (connectionActive) try {
(instance as Context).unbindService(this)
context.unbindService(this)
} catch (_: IllegalArgumentException) { } // ignore
connectionActive = false
if (instance.listenForDeath) binder?.unlinkToDeath(instance, 0)
......
......@@ -22,16 +22,18 @@ package com.github.shadowsocks
import android.app.KeyguardManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.VpnService
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.util.Log
import com.github.shadowsocks.App.Companion.app
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.getSystemService
import com.crashlytics.android.Crashlytics
import com.github.shadowsocks.aidl.IShadowsocksService
import com.github.shadowsocks.bg.BaseService
import com.github.shadowsocks.core.R
import com.github.shadowsocks.utils.broadcastReceiver
class VpnRequestActivity : AppCompatActivity(), ShadowsocksConnection.Interface {
......@@ -48,8 +50,7 @@ class VpnRequestActivity : AppCompatActivity(), ShadowsocksConnection.Interface
finish()
return
}
val km = getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
if (km.inKeyguardRestrictedInputMode()) {
if (getSystemService<KeyguardManager>()!!.isKeyguardLocked) {
receiver = broadcastReceiver { _, _ -> connection.connect() }
registerReceiver(receiver, IntentFilter(Intent.ACTION_USER_PRESENT))
} else connection.connect()
......@@ -62,7 +63,10 @@ class VpnRequestActivity : AppCompatActivity(), ShadowsocksConnection.Interface
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (resultCode == RESULT_OK) app.startService() else Log.e(TAG, "Failed to start VpnService")
if (resultCode == RESULT_OK) Core.startService() else {
Toast.makeText(this, R.string.vpn_permission_denied, Toast.LENGTH_LONG).show()
Crashlytics.log(Log.ERROR, TAG, "Failed to start VpnService from onActivityResult: $data")
}
finish()
}
......
......@@ -21,9 +21,10 @@
package com.github.shadowsocks.acl
import android.content.Context
import android.support.v7.util.SortedList
import android.util.Log
import com.github.shadowsocks.App.Companion.app
import androidx.recyclerview.widget.SortedList
import com.crashlytics.android.Crashlytics
import com.github.shadowsocks.Core
import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.Subnet
import com.github.shadowsocks.utils.asIterable
......@@ -45,25 +46,22 @@ class Acl {
val networkAclParser = "^IMPORT_URL\\s*<(.+)>\\s*$".toRegex()
fun getFile(id: String, context: Context = app.deviceContext) = File(context.filesDir, id + ".acl")
fun getFile(id: String, context: Context = Core.deviceStorage) = File(context.noBackupFilesDir, "$id.acl")
var customRules: Acl
get() {
val acl = Acl()
val str = DataStore.publicStore.getString(CUSTOM_RULES)
if (str != null) {
acl.fromReader(str.reader())
if (!acl.bypass) {
acl.subnets.clear()
acl.hostnames.clear()
acl.bypass = true
}
} else acl.bypass = true
if (str != null) acl.fromReader(str.reader(), true)
if (!acl.bypass) {
acl.bypass = true
acl.subnets.clear()
}
return acl
}
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())
set(value) = DataStore.publicStore.putString(CUSTOM_RULES,
if ((!value.bypass || value.subnets.size() == 0) && value.bypassHostnames.size() == 0 &&
value.proxyHostnames.size() == 0 && value.urls.size() == 0) null else value.toString())
fun save(id: String, acl: Acl) = getFile(id).writeText(acl.toString())
}
......@@ -88,31 +86,30 @@ class Acl {
override fun compareNonNull(o1: URL, o2: URL): Int = ordering.compare(o1, o2)
}
val hostnames = SortedList(String::class.java, StringSorter)
val bypassHostnames = SortedList(String::class.java, StringSorter)
val proxyHostnames = SortedList(String::class.java, StringSorter)
val subnets = SortedList(Subnet::class.java, SubnetSorter)
val urls = SortedList(URL::class.java, URLSorter)
var bypass = false
fun clear(): Acl {
hostnames.clear()
subnets.clear()
urls.clear()
return this
}
fun fromAcl(other: Acl): Acl {
clear()
for (item in other.hostnames.asIterable()) hostnames.add(item)
bypassHostnames.clear()
for (item in other.bypassHostnames.asIterable()) bypassHostnames.add(item)
proxyHostnames.clear()
for (item in other.proxyHostnames.asIterable()) proxyHostnames.add(item)
subnets.clear()
for (item in other.subnets.asIterable()) subnets.add(item)
urls.clear()
for (item in other.urls.asIterable()) urls.add(item)
bypass = other.bypass
return this
}
fun fromReader(reader: Reader, defaultBypass: Boolean = false): Acl {
clear()
bypassHostnames.clear()
proxyHostnames.clear()
subnets.clear()
urls.clear()
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 proxySubnets by lazy { SortedList(Subnet::class.java, SubnetSorter) }
var hostnames: SortedList<String>? = if (defaultBypass) proxyHostnames else bypassHostnames
......@@ -121,7 +118,7 @@ class Acl {
for (line in it) {
@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))
val input = blocks[0].trim()
when (input) {
......@@ -146,13 +143,12 @@ class Acl {
}
}
}
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)
return this
}
fun fromId(id: String): Acl = try {
fromReader(Acl.getFile(id).bufferedReader())
fromReader(getFile(id).bufferedReader())
} catch (_: IOException) { this }
fun flatten(depth: Int): Acl {
......@@ -160,18 +156,18 @@ class Acl {
val child = Acl()
try {
child.fromReader(url.openStream().bufferedReader(), bypass).flatten(depth - 1)
} catch (_: IOException) {
} catch (e: IOException) {
e.printStackTrace()
continue
}
if (bypass != child.bypass) {
Log.w(TAG, "Imported network ACL has a conflicting mode set. " +
Crashlytics.log(Log.WARN, TAG, "Imported network ACL has a conflicting mode set. " +
"This will probably not work as intended. URL: $url")
// rules for the different mode are discarded
child.hostnames.clear()
child.subnets.clear()
child.subnets.clear() // subnets for the different mode are discarded
child.bypass = bypass
}
for (item in child.hostnames.asIterable()) hostnames.add(item)
for (item in child.bypassHostnames.asIterable()) bypassHostnames.add(item)
for (item in child.proxyHostnames.asIterable()) proxyHostnames.add(item)
for (item in child.subnets.asIterable()) subnets.add(item)
}
urls.clear()
......@@ -180,11 +176,21 @@ class Acl {
override fun toString(): String {
val result = StringBuilder()
result.append(if (bypass) "[bypass_all]\n[proxy_list]\n" else "[proxy_all]\n[bypass_list]\n")
result.append(subnets.asIterable().joinToString("\n"))
result.append('\n')
result.append(hostnames.asIterable().joinToString("\n"))
result.append('\n')
result.append(if (bypass) "[bypass_all]\n" else "[proxy_all]\n")
val bypassList = (if (bypass) bypassHostnames.asIterable().asSequence() else
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')
}
if (proxyList.isNotEmpty()) {
result.append("[proxy_list]\n")
result.append(proxyList.joinToString("\n"))
result.append('\n')
}
result.append(urls.asIterable().joinToString("") { "#IMPORT_URL <$it>\n" })
return result.toString()
}
......
......@@ -20,49 +20,38 @@
package com.github.shadowsocks.acl
import android.util.Log
import com.evernote.android.job.Job
import com.evernote.android.job.JobCreator
import com.evernote.android.job.JobRequest
import android.content.Context
import androidx.work.*
import java.io.IOException
import java.net.URL
import java.util.concurrent.TimeUnit
class AclSyncJob(private val route: String) : Job() {
companion object : JobCreator {
private const val TAG = "AclSyncJob"
private const val MAX_RESCHEDULE = 3
class AclSyncer(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {
companion object {
private const val KEY_ROUTE = "route"
override fun create(tag: String): AclSyncJob? {
val parts = tag.split(':', limit = 2)
return when (parts[0]) {
TAG -> AclSyncJob(parts[1])
else -> {
Log.w(TAG, "Unknown job tag: " + tag)
null
}
}
}
fun schedule(route: String): Int = JobRequest.Builder(TAG + ':' + route)
.setExecutionWindow(TimeUnit.SECONDS.toMillis(10), TimeUnit.DAYS.toMillis(28))
.setRequirementsEnforced(true)
.setRequiredNetworkType(JobRequest.NetworkType.UNMETERED)
.setRequiresCharging(true)
.setUpdateCurrent(true)
.build().schedule()
fun schedule(route: String) = WorkManager.getInstance().enqueue(OneTimeWorkRequestBuilder<AclSyncer>().run {
setInputData(Data.Builder().putString(KEY_ROUTE, route).build())
setConstraints(Constraints.Builder()
.setRequiredNetworkType(NetworkType.UNMETERED)
.setRequiresCharging(true)
.build())
setInitialDelay(10, TimeUnit.SECONDS)
build()
})
}
override fun onRunJob(params: Params): Result = try {
override fun doWork(): Result = try {
val route = inputData.getString(KEY_ROUTE)!!
val acl = URL("https://shadowsocks.org/acl/android/v1/$route.acl").openStream().bufferedReader()
.use { it.readText() }
Acl.getFile(route).printWriter().use { it.write(acl) }
Result.SUCCESS
Result.success()
} catch (e: IOException) {
e.printStackTrace()
if (params.failureCount < AclSyncJob.MAX_RESCHEDULE) Result.RESCHEDULE else Result.FAILURE
Result.retry()
} catch (e: Exception) { // unknown failures, probably shouldn't retry
e.printStackTrace()
Result.FAILURE
Result.failure()
}
}
......@@ -20,10 +20,12 @@
package com.github.shadowsocks.bg
import android.system.ErrnoException
import android.system.Os
import android.text.TextUtils
import android.util.Log
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.JniHelper
import com.crashlytics.android.Crashlytics
import com.github.shadowsocks.Core.app
import java.io.File
import java.io.FileNotFoundException
......@@ -34,7 +36,7 @@ object Executable {
const val TUN2SOCKS = "libtun2socks.so"
const val OVERTURE = "liboverture.so"
val EXECUTABLES = setOf(SS_LOCAL, SS_TUNNEL, REDSOCKS, TUN2SOCKS, OVERTURE)
private val EXECUTABLES = setOf(SS_LOCAL, SS_TUNNEL, REDSOCKS, TUN2SOCKS, OVERTURE)
fun killAll() {
for (process in File("/proc").listFiles { _, name -> TextUtils.isDigitsOnly(name) }) {
......@@ -43,9 +45,14 @@ object Executable {
} catch (ignore: FileNotFoundException) {
continue
}.split(Character.MIN_VALUE, limit = 2).first())
if (exe.parent == app.applicationInfo.nativeLibraryDir && EXECUTABLES.contains(exe.name)) {
val errno = JniHelper.sigkill(process.name.toInt())
if (errno != 0) Log.w("kill", "SIGKILL ${exe.absolutePath} (${process.name}) failed with $errno")
if (exe.parent == app.applicationInfo.nativeLibraryDir && EXECUTABLES.contains(exe.name)) try {
Os.kill(process.name.toInt(), 9) // SIGKILL
} catch (e: ErrnoException) {
if (e.errno != 3) { // ESRCH
e.printStackTrace()
Crashlytics.log(Log.WARN, "kill", "SIGKILL ${exe.absolutePath} (${process.name}) failed")
Crashlytics.logException(e)
}
}
}
}
......
......@@ -22,49 +22,58 @@ package com.github.shadowsocks.bg
import android.os.Build
import android.os.SystemClock
import android.system.ErrnoException
import android.system.Os
import android.util.Log
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.BuildConfig
import com.github.shadowsocks.JniHelper
import com.crashlytics.android.Crashlytics
import com.github.shadowsocks.Core
import com.github.shadowsocks.utils.Commandline
import com.github.shadowsocks.utils.thread
import java.io.File
import java.io.IOException
import java.io.InputStream
import java.util.concurrent.Semaphore
import java.util.concurrent.ArrayBlockingQueue
import java.util.concurrent.atomic.AtomicReference
import java.util.concurrent.TimeUnit
class GuardedProcess(private val cmd: List<String>) {
companion object {
private const val TAG = "GuardedProcess"
class GuardedProcessPool {
companion object Dummy : IOException("Oopsie the developer has made a no-no") {
private const val TAG = "GuardedProcessPool"
private val ProcessImpl by lazy { Class.forName("java.lang.ProcessManager\$ProcessImpl") }
private val pid by lazy { ProcessImpl.getDeclaredField("pid").apply { isAccessible = true } }
private val exitValueMutex by lazy {
ProcessImpl.getDeclaredField("exitValueMutex").apply { isAccessible = true }
}
}
private lateinit var guardThread: Thread
@Volatile
private var isDestroyed = false
@Volatile
private lateinit var process: Process
private val name = File(cmd.first()).nameWithoutExtension
private fun streamLogger(input: InputStream, logger: (String, String) -> Int) = thread("StreamLogger-$name") {
try {
input.bufferedReader().useLines { it.forEach { logger(TAG, it) } }
} catch (_: IOException) { } // ignore
}
private inner class Guard(private val cmd: List<String>, private val onRestartCallback: (() -> Unit)?) {
val cmdName = File(cmd.first()).nameWithoutExtension
val excQueue = ArrayBlockingQueue<IOException>(1) // ArrayBlockingQueue doesn't want null
private var pushed = false
private fun streamLogger(input: InputStream, logger: (String, String) -> Int) =
thread("StreamLogger-$cmdName") {
try {
input.bufferedReader().forEachLine { logger(TAG, it) }
} catch (_: IOException) { } // ignore
}
private fun pushException(ioException: IOException?) {
if (pushed) return
excQueue.put(ioException ?: Dummy)
pushed = true
}
fun start(onRestartCallback: (() -> Unit)? = null): GuardedProcess {
val semaphore = Semaphore(1)
semaphore.acquire()
var ioException: IOException? = null
guardThread = thread("GuardThread-$name") {
fun looper(host: HashSet<Thread>) {
var process: Process? = null
try {
var callback: (() -> Unit)? = null
while (!isDestroyed) {
if (BuildConfig.DEBUG) Log.d(TAG, "start process: " + Commandline.toString(cmd))
while (guardThreads.get() === host) {
Crashlytics.log(Log.DEBUG, TAG, "start process: " + Commandline.toString(cmd))
val startTime = SystemClock.elapsedRealtime()
process = ProcessBuilder(cmd)
.redirectErrorStream(true)
.directory(app.deviceContext.filesDir)
.directory(Core.deviceStorage.noBackupFilesDir)
.start()
streamLogger(process.inputStream, Log::i)
......@@ -72,44 +81,79 @@ class GuardedProcess(private val cmd: List<String>) {
if (callback == null) callback = onRestartCallback else callback()
semaphore.release()
pushException(null)
process.waitFor()
synchronized(this) {
if (SystemClock.elapsedRealtime() - startTime < 1000) {
Log.w(TAG, "process exit too fast, stop guard: " + Commandline.toString(cmd))
isDestroyed = true
}
if (SystemClock.elapsedRealtime() - startTime < 1000) {
Crashlytics.log(Log.WARN, TAG, "process exit too fast, stop guard: $cmdName")
break
}
}
} catch (_: InterruptedException) {
if (BuildConfig.DEBUG) Log.d(TAG, "thread interrupt, destroy process: " + Commandline.toString(cmd))
destroyProcess()
Crashlytics.log(Log.DEBUG, TAG, "thread interrupt, destroy process: $cmdName")
} catch (e: IOException) {
ioException = e
pushException(e)
} finally {
semaphore.release()
if (process != null) {
if (Build.VERSION.SDK_INT < 24) {
val pid = pid.get(process) as Int
try {
Os.kill(pid, 15) // SIGTERM
} catch (e: ErrnoException) {
if (e.errno != 3) throw e // ESRCH
}
val mutex = exitValueMutex.get(process) as Object
synchronized(mutex) {
try {
process.exitValue()
} catch (e: IllegalThreadStateException) {
mutex.wait(500)
}
}
}
process.destroy() // kill the process
if (Build.VERSION.SDK_INT >= 26) {
val isKilled = process.waitFor(1L, TimeUnit.SECONDS) // wait for 1 second
if (!isKilled) {
process.destroyForcibly() // Force to kill the process if it's still alive
}
}
process.waitFor() // ensure the process is destroyed
}
pushException(null)
}
}
semaphore.acquire()
if (ioException != null) throw ioException!!
return this
}
fun destroy() {
isDestroyed = true
guardThread.interrupt()
destroyProcess()
try {
guardThread.join()
} catch (_: InterruptedException) { }
/**
* This is an indication of which thread pool is being active.
* Reading/writing this collection still needs an additional lock to prevent concurrent modification.
*/
private val guardThreads = AtomicReference<HashSet<Thread>>(HashSet())
fun start(cmd: List<String>, onRestartCallback: (() -> Unit)? = null): GuardedProcessPool {
val guard = Guard(cmd, onRestartCallback)
val guardThreads = guardThreads.get()
synchronized(guardThreads) {
guardThreads.add(thread("GuardThread-${guard.cmdName}") {
guard.looper(guardThreads)
})
}
val ioException = guard.excQueue.take()
if (ioException !== Dummy) throw ioException
return this
}
private fun destroyProcess() {
if (Build.VERSION.SDK_INT < 24) @Suppress("DEPRECATION") {
JniHelper.sigtermCompat(process)
JniHelper.waitForCompat(process, 500)
fun killAll() {
val guardThreads = guardThreads.getAndSet(HashSet())
synchronized(guardThreads) {
guardThreads.forEach { it.interrupt() }
try {
guardThreads.forEach { it.join() }
} catch (_: InterruptedException) { }
}
process.destroy()
}
}
......@@ -20,94 +20,74 @@
package com.github.shadowsocks.bg
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.Core
import com.github.shadowsocks.Core.app
import com.github.shadowsocks.acl.Acl
import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.parseNumericAddress
import org.json.JSONArray
import org.json.JSONObject
import java.io.File
import java.net.Inet6Address
import java.util.*
import org.json.JSONArray
import org.json.JSONObject
/**
* This object also uses WeakMap to simulate the effects of multi-inheritance, but more lightweight.
*/
object LocalDnsService {
interface Interface : BaseService.Interface {
var overtureProcess: GuardedProcess?
get() = overtureProcesses[this]
set(value) {
if (value == null) overtureProcesses.remove(this) else overtureProcesses[this] = value
}
override fun startNativeProcesses() {
super.startNativeProcesses()
val data = data
val profile = data.profile!!
fun makeDns(name: String, address: String, timeout: Int, edns: Boolean = true): JSONObject {
val dns = JSONObject()
.put("Name", name)
.put("Address", when (address.parseNumericAddress()) {
fun makeDns(name: String, address: String, timeout: Int, edns: Boolean = true) = JSONObject().apply {
put("Name", name)
put("Address", when (address.parseNumericAddress()) {
is Inet6Address -> "[$address]"
else -> address
})
.put("Timeout", timeout)
.put("EDNSClientSubnet", JSONObject().put("Policy", "disable"))
if (edns) dns
.put("Protocol", "tcp")
.put("Socks5Address", "127.0.0.1:" + DataStore.portProxy)
else dns.put("Protocol", "udp")
return dns
put("Timeout", timeout)
put("EDNSClientSubnet", JSONObject().put("Policy", "disable"))
put("Protocol", if (edns) {
put("Socks5Address", "127.0.0.1:" + DataStore.portProxy)
"tcp"
} else "udp")
}
fun buildOvertureConfig(file: String): String {
val config = JSONObject()
.put("BindAddress", "127.0.0.1:" + DataStore.portLocalDns)
.put("RedirectIPv6Record", true)
.put("DomainBase64Decode", false)
.put("HostsFile", "hosts")
.put("MinimumTTL", 120)
.put("CacheSize", 4096)
val remoteDns = JSONArray(profile.remoteDns.split(",")
.mapIndexed { i, dns -> makeDns("UserDef-$i", dns.trim() + ":53", 9) })
val localDns = JSONArray(arrayOf(
makeDns("Primary-1", "208.67.222.222:443", 3, false),
makeDns("Primary-2", "119.29.29.29:53", 3, false),
makeDns("Primary-3", "114.114.114.114:53", 3, false)
))
when (profile.route) {
Acl.BYPASS_CHN, Acl.BYPASS_LAN_CHN, Acl.GFWLIST, Acl.CUSTOM_RULES -> config
.put("PrimaryDNS", localDns)
.put("AlternativeDNS", remoteDns)
.put("IPNetworkFile", "china_ip_list.txt")
Acl.CHINALIST -> config
.put("PrimaryDNS", localDns)
.put("AlternativeDNS", remoteDns)
else -> config
.put("PrimaryDNS", remoteDns)
fun buildOvertureConfig(file: String) = file.also {
File(Core.deviceStorage.noBackupFilesDir, it).writeText(JSONObject().run {
put("BindAddress", "${DataStore.listenAddress}:${DataStore.portLocalDns}")
put("RedirectIPv6Record", true)
put("DomainBase64Decode", false)
put("HostsFile", "hosts")
put("MinimumTTL", 120)
put("CacheSize", 4096)
val remoteDns = JSONArray(profile.remoteDns.split(",")
.mapIndexed { i, dns -> makeDns("UserDef-$i", dns.trim() + ":53", 12) })
val localDns = JSONArray(arrayOf(
makeDns("Primary-1", "208.67.222.222:443", 9, false),
makeDns("Primary-2", "119.29.29.29:53", 9, false),
makeDns("Primary-3", "114.114.114.114:53", 9, false)))
when (profile.route) {
Acl.BYPASS_CHN, Acl.BYPASS_LAN_CHN, Acl.GFWLIST, Acl.CUSTOM_RULES -> {
put("PrimaryDNS", localDns)
put("AlternativeDNS", remoteDns)
put("IPNetworkFile", "china_ip_list.txt")
}
Acl.CHINALIST -> {
put("PrimaryDNS", localDns)
put("AlternativeDNS", remoteDns)
}
else -> {
put("PrimaryDNS", remoteDns)
// no need to setup AlternativeDNS in Acl.ALL/BYPASS_LAN mode
.put("OnlyPrimaryDNS", true)
}
File(app.deviceContext.filesDir, file).writeText(config.toString())
return file
put("OnlyPrimaryDNS", true)
}
}
toString()
})
}
if (!profile.udpdns) overtureProcess = GuardedProcess(buildAdditionalArguments(arrayListOf(
if (!profile.udpdns) data.processes.start(buildAdditionalArguments(arrayListOf(
File(app.applicationInfo.nativeLibraryDir, Executable.OVERTURE).absolutePath,
"-c", buildOvertureConfig("overture.conf")
))).start()
}
override fun killProcesses() {
super.killProcesses()
overtureProcess?.destroy()
overtureProcess = null
"-c", buildOvertureConfig("overture.conf"))))
}
}
private val overtureProcesses = WeakHashMap<Interface, GuardedProcess>()
}
......@@ -23,14 +23,13 @@ package com.github.shadowsocks.bg
import android.net.LocalServerSocket
import android.net.LocalSocket
import android.net.LocalSocketAddress
import android.util.Log
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.utils.printLog
import java.io.File
import java.io.IOException
abstract class LocalSocketListener(protected val tag: String) : Thread(tag) {
init {
setUncaughtExceptionHandler(app::track)
setUncaughtExceptionHandler { _, t -> printLog(t) }
}
protected abstract val socketFile: File
......@@ -48,15 +47,14 @@ abstract class LocalSocketListener(protected val tag: String) : Thread(tag) {
localSocket.bind(LocalSocketAddress(socketFile.absolutePath, LocalSocketAddress.Namespace.FILESYSTEM))
LocalServerSocket(localSocket.fileDescriptor)
} catch (e: IOException) {
Log.e(tag, "unable to bind", e)
printLog(e)
return
}
while (running) {
try {
serverSocket.accept()
} catch (e: IOException) {
Log.e(tag, "Error when accept socket", e)
app.track(e)
printLog(e)
null
}?.use(this::accept)
}
......
......@@ -22,7 +22,6 @@ package com.github.shadowsocks.bg
import android.app.Service
import android.content.Intent
import android.os.IBinder
/**
* Shadowsocks service at its minimum.
......@@ -36,7 +35,7 @@ class ProxyService : Service(), BaseService.Interface {
override fun createNotification(profileName: String): ServiceNotification =
ServiceNotification(this, profileName, "service-proxy", true)
override fun onBind(intent: Intent): IBinder? = super.onBind(intent)
override fun onBind(intent: Intent) = super.onBind(intent)
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int =
super<BaseService.Interface>.onStartCommand(intent, flags, startId)
}
......@@ -29,11 +29,13 @@ import android.content.Intent
import android.content.IntentFilter
import android.os.Build
import android.os.PowerManager
import android.support.v4.app.NotificationCompat
import android.support.v4.content.ContextCompat
import com.github.shadowsocks.MainActivity
import com.github.shadowsocks.R
import android.text.format.Formatter
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService
import com.github.shadowsocks.Core
import com.github.shadowsocks.aidl.IShadowsocksServiceCallback
import com.github.shadowsocks.core.R
import com.github.shadowsocks.utils.Action
import com.github.shadowsocks.utils.broadcastReceiver
import java.util.*
......@@ -46,23 +48,21 @@ import java.util.*
*/
class ServiceNotification(private val service: BaseService.Interface, profileName: String,
channel: String, private val visible: Boolean = false) {
private val keyGuard = (service as Context).getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
private val nm by lazy {
(service as Context).getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
}
private val callback by lazy {
private val keyGuard = (service as Context).getSystemService<KeyguardManager>()!!
private val nm by lazy { (service as Context).getSystemService<NotificationManager>()!! }
private val callback: IShadowsocksServiceCallback by lazy {
object : IShadowsocksServiceCallback.Stub() {
override fun stateChanged(state: Int, profileName: String?, msg: String?) { } // ignore
override fun trafficUpdated(profileId: Int, txRate: Long, rxRate: Long, txTotal: Long, rxTotal: Long) {
override fun trafficUpdated(profileId: Long, txRate: Long, rxRate: Long, txTotal: Long, rxTotal: Long) {
service as Context
val txr = service.getString(R.string.speed, TrafficMonitor.formatTraffic(txRate))
val rxr = service.getString(R.string.speed, TrafficMonitor.formatTraffic(rxRate))
val txr = service.getString(R.string.speed, Formatter.formatFileSize(service, txRate))
val rxr = service.getString(R.string.speed, Formatter.formatFileSize(service, rxRate))
builder.setContentText("$txr↑\t$rxr↓")
style.bigText(service.getString(R.string.stat_summary).format(Locale.ENGLISH, txr, rxr,
TrafficMonitor.formatTraffic(txTotal), TrafficMonitor.formatTraffic(rxTotal)))
style.bigText(service.getString(R.string.stat_summary, txr, rxr,
Formatter.formatFileSize(service, txTotal), Formatter.formatFileSize(service, rxTotal)))
show()
}
override fun trafficPersisted(profileId: Int) { }
override fun trafficPersisted(profileId: Long) { }
}
}
private val lockReceiver = broadcastReceiver { _, intent -> update(intent.action) }
......@@ -73,17 +73,17 @@ class ServiceNotification(private val service: BaseService.Interface, profileNam
.setColor(ContextCompat.getColor(service, R.color.material_primary_500))
.setTicker(service.getString(R.string.forward_success))
.setContentTitle(profileName)
.setContentIntent(MainActivity.pendingIntent(service))
.setContentIntent(Core.configureIntent(service))
.setSmallIcon(R.drawable.ic_service_active)
private val style = NotificationCompat.BigTextStyle(builder)
private val style = NotificationCompat.BigTextStyle(builder).bigText("")
private var isVisible = true
init {
service as Context
if (Build.VERSION.SDK_INT < 24) builder.addAction(R.drawable.ic_navigation_close,
service.getString(R.string.stop), PendingIntent.getBroadcast(service, 0, Intent(Action.CLOSE), 0))
val power = service.getSystemService(Context.POWER_SERVICE) as PowerManager
update(if (power.isInteractive) Intent.ACTION_SCREEN_ON else Intent.ACTION_SCREEN_OFF, true)
update(if (service.getSystemService<PowerManager>()?.isInteractive != false)
Intent.ACTION_SCREEN_ON else Intent.ACTION_SCREEN_OFF, true)
val screenFilter = IntentFilter()
screenFilter.addAction(Intent.ACTION_SCREEN_ON)
screenFilter.addAction(Intent.ACTION_SCREEN_OFF)
......@@ -91,14 +91,14 @@ class ServiceNotification(private val service: BaseService.Interface, profileNam
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) {
Intent.ACTION_SCREEN_OFF -> {
setVisible(false, forceShow)
unregisterCallback() // unregister callback to save battery
}
Intent.ACTION_SCREEN_ON -> {
setVisible(visible && !keyGuard.inKeyguardRestrictedInputMode(), forceShow)
setVisible(visible && !keyGuard.isKeyguardLocked, forceShow)
service.data.binder.registerCallback(callback)
service.data.binder.startListeningForBandwidth(callback)
callbackRegistered = true
......
......@@ -21,9 +21,6 @@
package com.github.shadowsocks.bg
import android.os.SystemClock
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.R
import java.text.DecimalFormat
object TrafficMonitor {
// Bytes per second
......@@ -41,19 +38,6 @@ object TrafficMonitor {
@Volatile
private var dirty = true
private val units = arrayOf("KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB", "BB", "NB", "DB", "CB")
private val numberFormat = DecimalFormat("@@@")
fun formatTraffic(size: Long): String {
var n = size.toDouble()
var i = -1
while (n >= 999.5) {
n /= 1024
++i
}
return if (i < 0) "$size ${app.resources.getQuantityString(R.plurals.bytes, size.toInt())}"
else "${numberFormat.format(n)} ${units[i]}"
}
fun updateRate(): Boolean {
val now = SystemClock.elapsedRealtime()
val delta = now - timestampLast
......
......@@ -21,15 +21,15 @@
package com.github.shadowsocks.bg
import android.net.LocalSocket
import android.util.Log
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.Core
import com.github.shadowsocks.utils.printLog
import java.io.File
import java.io.IOException
import java.nio.ByteBuffer
import java.nio.ByteOrder
class TrafficMonitorThread : LocalSocketListener("TrafficMonitorThread") {
override val socketFile = File(app.deviceContext.filesDir, "stat_path")
override val socketFile = File(Core.deviceStorage.noBackupFilesDir, "stat_path")
override fun accept(socket: LocalSocket) {
try {
......@@ -38,8 +38,7 @@ class TrafficMonitorThread : LocalSocketListener("TrafficMonitorThread") {
val stat = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN)
TrafficMonitor.update(stat.getLong(0), stat.getLong(8))
} catch (e: IOException) {
Log.e(tag, "Error when recv traffic stat", e)
app.track(e)
printLog(e)
}
}
}
......@@ -22,8 +22,7 @@ package com.github.shadowsocks.bg
import android.app.Service
import android.content.Intent
import android.os.IBinder
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.Core
import com.github.shadowsocks.preference.DataStore
import java.io.File
......@@ -36,26 +35,24 @@ class TransproxyService : Service(), LocalDnsService.Interface {
override fun createNotification(profileName: String): ServiceNotification =
ServiceNotification(this, profileName, "service-transproxy", true)
override fun onBind(intent: Intent): IBinder? = super.onBind(intent)
override fun onBind(intent: Intent) = super.onBind(intent)
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int =
super<LocalDnsService.Interface>.onStartCommand(intent, flags, startId)
private var sstunnelProcess: GuardedProcess? = null
private var redsocksProcess: GuardedProcess? = null
private fun startDNSTunnel() {
val cmd = arrayListOf(File(applicationInfo.nativeLibraryDir, Executable.SS_TUNNEL).absolutePath,
"-t", "10",
"-b", "127.0.0.1",
"-b", DataStore.listenAddress,
"-u",
"-l", DataStore.portLocalDns.toString(), // ss-tunnel listens on the same port as overture
"-L", data.profile!!.remoteDns.split(",").first().trim() + ":53",
"-c", data.shadowsocksConfigFile!!.absolutePath) // config is already built by BaseService.Interface
sstunnelProcess = GuardedProcess(cmd).start()
if (DataStore.tcpFastOpen) cmd += "--fast-open"
data.processes.start(cmd)
}
private fun startRedsocksDaemon() {
File(app.deviceContext.filesDir, "redsocks.conf").writeText("""base {
File(Core.deviceStorage.noBackupFilesDir, "redsocks.conf").writeText("""base {
log_debug = off;
log_info = off;
log = stderr;
......@@ -63,17 +60,15 @@ class TransproxyService : Service(), LocalDnsService.Interface {
redirector = iptables;
}
redsocks {
local_ip = 127.0.0.1;
local_ip = ${DataStore.listenAddress};
local_port = ${DataStore.portTransproxy};
ip = 127.0.0.1;
port = ${DataStore.portProxy};
type = socks5;
}
""")
redsocksProcess = GuardedProcess(arrayListOf(
File(applicationInfo.nativeLibraryDir, Executable.REDSOCKS).absolutePath,
"-c", "redsocks.conf")
).start()
data.processes.start(listOf(
File(applicationInfo.nativeLibraryDir, Executable.REDSOCKS).absolutePath, "-c", "redsocks.conf"))
}
override fun startNativeProcesses() {
......@@ -81,12 +76,4 @@ redsocks {
super.startNativeProcesses()
if (data.profile!!.udpdns) startDNSTunnel()
}
override fun killProcesses() {
super.killProcesses()
sstunnelProcess?.destroy()
sstunnelProcess = null
redsocksProcess?.destroy()
redsocksProcess = null
}
}
......@@ -22,24 +22,23 @@ package com.github.shadowsocks.bg
import android.annotation.TargetApi
import android.app.Service
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.*
import android.os.Build
import android.os.IBinder
import android.os.ParcelFileDescriptor
import android.support.v4.os.BuildCompat
import android.util.Log
import com.github.shadowsocks.App.Companion.app
import android.system.ErrnoException
import android.system.Os
import androidx.core.content.getSystemService
import com.github.shadowsocks.Core
import com.github.shadowsocks.JniHelper
import com.github.shadowsocks.MainActivity
import com.github.shadowsocks.R
import com.github.shadowsocks.VpnRequestActivity
import com.github.shadowsocks.acl.Acl
import com.github.shadowsocks.core.R
import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.Subnet
import com.github.shadowsocks.utils.parseNumericAddress
import com.github.shadowsocks.utils.printLog
import java.io.File
import java.io.FileDescriptor
import java.io.IOException
......@@ -50,9 +49,12 @@ import android.net.VpnService as BaseVpnService
class VpnService : BaseVpnService(), LocalDnsService.Interface {
companion object {
private const val VPN_MTU = 1500
private const val PRIVATE_VLAN = "26.26.26.%s"
private const val PRIVATE_VLAN = "172.19.0.%s"
private const val PRIVATE_VLAN6 = "fdfe:dcba:9876::%s"
/**
* https://android.googlesource.com/platform/prebuilts/runtime/+/94fec32/appcompat/hiddenapi-light-greylist.txt#9466
*/
private val getInt: Method = FileDescriptor::class.java.getDeclaredMethod("getInt$")
/**
......@@ -72,35 +74,27 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
}
private inner class ProtectWorker : LocalSocketListener("ShadowsocksVpnThread") {
override val socketFile: File = File(app.deviceContext.filesDir, "protect_path")
override val socketFile: File = File(Core.deviceStorage.noBackupFilesDir, "protect_path")
override fun accept(socket: LocalSocket) {
var success = false
try {
socket.inputStream.read()
val fd = socket.ancillaryFileDescriptors!!.single()!!
val fdInt = getInt.invoke(fd) as Int
try {
val network = underlyingNetwork
success = if (network != null && Build.VERSION.SDK_INT >= 23) {
network.bindSocket(fd)
true
} else protect(fdInt)
} catch (e: Exception) {
Log.e(tag, "Error when protect socket", e)
app.track(e)
} finally {
JniHelper.close(fdInt) // Trick to close file decriptor
}
} catch (e: Exception) {
Log.e(tag, "Error when receiving ancillary fd", e)
app.track(e)
}
try {
socket.outputStream.write(if (success) 0 else 1)
socket.outputStream.write(if (try {
val network = underlyingNetwork
if (network != null && Build.VERSION.SDK_INT >= 23) {
network.bindSocket(fd)
true
} else protect(getInt.invoke(fd) as Int)
} finally {
try {
Os.close(fd)
} catch (e: ErrnoException) {
printLog(e)
}
}) 0 else 1)
} catch (e: IOException) {
Log.e(tag, "Error when returning result in protect", e)
app.track(e)
printLog(e)
}
}
}
......@@ -116,7 +110,6 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
private var conn: ParcelFileDescriptor? = null
private var worker: ProtectWorker? = null
private var tun2socksProcess: GuardedProcess? = null
private var underlyingNetwork: Network? = null
@TargetApi(28)
set(value) {
......@@ -124,7 +117,7 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
field = value
}
private val connectivity by lazy { getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager }
private val connectivity by lazy { getSystemService<ConnectivityManager>()!! }
@TargetApi(28)
private val defaultNetworkCallback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
......@@ -140,7 +133,7 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
}
private var listeningForDefaultNetwork = false
override fun onBind(intent: Intent): IBinder? = when (intent.action) {
override fun onBind(intent: Intent) = when (intent.action) {
SERVICE_INTERFACE -> super<BaseVpnService>.onBind(intent)
else -> super<LocalDnsService.Interface>.onBind(intent)
}
......@@ -155,8 +148,6 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
worker?.stopThread()
worker = null
super.killProcesses()
tun2socksProcess?.destroy()
tun2socksProcess = null
conn?.close()
conn = null
}
......@@ -178,8 +169,7 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
super.startNativeProcesses()
val fd = startVpn()
if (!sendFd(fd)) throw IOException("sendFd failed")
sendFd(startVpn())
}
override fun buildAdditionalArguments(cmd: ArrayList<String>): ArrayList<String> {
......@@ -190,7 +180,7 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
private fun startVpn(): Int {
val profile = data.profile!!
val builder = Builder()
.setConfigureIntent(MainActivity.pendingIntent(this))
.setConfigureIntent(Core.configureIntent(this))
.setSession(profile.formattedName)
.setMtu(VPN_MTU)
.addAddress(PRIVATE_VLAN.format(Locale.ENGLISH, "1"), 24)
......@@ -211,7 +201,7 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
if (profile.bypass) builder.addDisallowedApplication(it)
else builder.addAllowedApplication(it)
} catch (ex: PackageManager.NameNotFoundException) {
Log.e(tag, "Invalid package name", ex)
printLog(ex)
}
}
if (!profile.bypass) builder.addAllowedApplication(me)
......@@ -233,7 +223,7 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
this.conn = conn
val fd = conn.fd
if (BuildCompat.isAtLeastP()) {
if (Build.VERSION.SDK_INT >= 28) {
// we want REQUEST here instead of LISTEN
connectivity.requestNetwork(defaultNetworkRequest, defaultNetworkCallback)
listeningForDefaultNetwork = true
......@@ -242,7 +232,7 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
val cmd = arrayListOf(File(applicationInfo.nativeLibraryDir, Executable.TUN2SOCKS).absolutePath,
"--netif-ipaddr", PRIVATE_VLAN.format(Locale.ENGLISH, "2"),
"--netif-netmask", "255.255.255.0",
"--socks-server-addr", "127.0.0.1:${DataStore.portProxy}",
"--socks-server-addr", "${DataStore.listenAddress}:${DataStore.portProxy}",
"--tunfd", fd.toString(),
"--tunmtu", VPN_MTU.toString(),
"--sock-path", "sock_path",
......@@ -256,19 +246,27 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
cmd += "--dnsgw"
cmd += "127.0.0.1:${DataStore.portLocalDns}"
}
tun2socksProcess = GuardedProcess(cmd).start { sendFd(fd) }
data.processes.start(cmd, onRestartCallback = {
try {
sendFd(fd)
} catch (e: ErrnoException) {
stopRunner(true, e.message)
}
})
return fd
}
private fun sendFd(fd: Int): Boolean {
if (fd != -1) {
var tries = 0
while (tries < 10) {
Thread.sleep(30L shl tries)
if (JniHelper.sendFd(fd, File(app.deviceContext.filesDir, "sock_path").absolutePath) != -1) return true
tries += 1
}
private fun sendFd(fd: Int) {
if (fd == -1) throw IOException("Invalid fd (-1)")
var tries = 0
val path = File(Core.deviceStorage.noBackupFilesDir, "sock_path").absolutePath
while (true) try {
Thread.sleep(50L shl tries)
JniHelper.sendFd(fd, path)
return
} catch (e: ErrnoException) {
if (tries > 5) throw e
tries += 1
}
return false
}
}
......@@ -20,37 +20,54 @@
package com.github.shadowsocks.database
import com.j256.ormlite.field.DataType
import com.j256.ormlite.field.DatabaseField
import androidx.room.*
import java.io.ByteArrayOutputStream
import java.nio.ByteBuffer
@Entity
class KeyValuePair() {
companion object {
const val TYPE_UNINITIALIZED = 0
const val TYPE_BOOLEAN = 1
const val TYPE_FLOAT = 2
@Deprecated("Use TYPE_LONG.")
const val TYPE_INT = 3
const val TYPE_LONG = 4
const val TYPE_STRING = 5
const val TYPE_STRING_SET = 6
}
@DatabaseField(id = true)
var key: String? = ""
@DatabaseField
@androidx.room.Dao
interface Dao {
@Query("SELECT * FROM `KeyValuePair` WHERE `key` = :key")
operator fun get(key: String): KeyValuePair?
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun put(value: KeyValuePair): Long
@Query("DELETE FROM `KeyValuePair` WHERE `key` = :key")
fun delete(key: String): Int
}
@PrimaryKey
var key: String = ""
var valueType: Int = TYPE_UNINITIALIZED
@DatabaseField(dataType = DataType.BYTE_ARRAY)
var value: ByteArray = ByteArray(0)
val boolean: Boolean?
get() = if (valueType == TYPE_BOOLEAN) ByteBuffer.wrap(value).get() != 0.toByte() else null
val float: Float?
get() = if (valueType == TYPE_FLOAT) ByteBuffer.wrap(value).float else null
@Suppress("DEPRECATION")
@Deprecated("Use long.", ReplaceWith("long"))
val int: Int?
get() = if (valueType == TYPE_INT) ByteBuffer.wrap(value).int else null
val long: Long?
get() = if (valueType == TYPE_LONG) ByteBuffer.wrap(value).long else null
val long: Long? get() = when (valueType) {
@Suppress("DEPRECATION")
TYPE_INT -> ByteBuffer.wrap(value).int.toLong()
TYPE_LONG -> ByteBuffer.wrap(value).long
else -> null
}
val string: String?
get() = if (valueType == TYPE_STRING) String(value) else null
val stringSet: Set<String>?
......@@ -65,7 +82,8 @@ class KeyValuePair() {
result
} else null
constructor(key: String?) : this() {
@Ignore
constructor(key: String) : this() {
this.key = key
}
......@@ -80,6 +98,8 @@ class KeyValuePair() {
this.value = ByteBuffer.allocate(4).putFloat(value).array()
return this
}
@Suppress("DEPRECATION")
@Deprecated("Use long.")
fun put(value: Int): KeyValuePair {
valueType = TYPE_INT
this.value = ByteBuffer.allocate(4).putInt(value).array()
......
......@@ -20,42 +20,39 @@
package com.github.shadowsocks.database
import android.database.sqlite.SQLiteDatabase
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.acl.Acl
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.sqlite.db.SupportSQLiteDatabase
import com.github.shadowsocks.Core.app
import com.github.shadowsocks.database.migration.RecreateSchemaMigration
import com.github.shadowsocks.utils.Key
import com.j256.ormlite.android.AndroidDatabaseConnection
import com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper
import com.j256.ormlite.dao.Dao
import com.j256.ormlite.support.ConnectionSource
import com.j256.ormlite.table.TableUtils
object PublicDatabase : OrmLiteSqliteOpenHelper(app.deviceContext, Key.DB_PUBLIC, null, 2) {
@Suppress("UNCHECKED_CAST")
val kvPairDao: Dao<KeyValuePair, String?> by lazy { getDao(KeyValuePair::class.java) as Dao<KeyValuePair, String?> }
override fun onCreate(database: SQLiteDatabase?, connectionSource: ConnectionSource?) {
TableUtils.createTable(connectionSource, KeyValuePair::class.java)
}
override fun onUpgrade(database: SQLiteDatabase?, connectionSource: ConnectionSource?,
oldVersion: Int, newVersion: Int) {
if (oldVersion < 1) {
PrivateDatabase.kvPairDao.queryBuilder().where().`in`("key",
Key.id, Key.tfo, Key.serviceMode, Key.portProxy, Key.portLocalDns, Key.portTransproxy).query()
.forEach { kvPairDao.createOrUpdate(it) }
@Database(entities = [Profile::class, KeyValuePair::class], version = 26)
abstract class PrivateDatabase : RoomDatabase() {
companion object {
private val instance by lazy {
Room.databaseBuilder(app, PrivateDatabase::class.java, Key.DB_PROFILE)
.addMigrations(
Migration26
)
.fallbackToDestructiveMigration()
.allowMainThreadQueries()
.build()
}
if (oldVersion < 2) {
kvPairDao.createOrUpdate(KeyValuePair(Acl.CUSTOM_RULES).put(Acl().fromId(Acl.CUSTOM_RULES).toString()))
}
val profileDao get() = instance.profileDao()
val kvPairDao get() = instance.keyValuePairDao()
}
abstract fun profileDao(): Profile.Dao
abstract fun keyValuePairDao(): KeyValuePair.Dao
override fun onDowngrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
val connection = AndroidDatabaseConnection(db, true)
connectionSource.saveSpecialConnection(connection)
TableUtils.dropTable<KeyValuePair, String?>(connectionSource, KeyValuePair::class.java, true)
onCreate(db, connectionSource)
connectionSource.clearSpecialConnection(connection)
private object Migration26 : RecreateSchemaMigration(25, 26, "Profile",
"(`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT, `host` TEXT NOT NULL, `remotePort` INTEGER NOT NULL, `password` TEXT NOT NULL, `method` TEXT NOT NULL, `route` TEXT NOT NULL, `remoteDns` TEXT NOT NULL, `proxyApps` INTEGER NOT NULL, `bypass` INTEGER NOT NULL, `udpdns` INTEGER NOT NULL, `ipv6` INTEGER NOT NULL, `individual` TEXT NOT NULL, `tx` INTEGER NOT NULL, `rx` INTEGER NOT NULL, `userOrder` INTEGER NOT NULL, `plugin` TEXT)",
"`id`, `name`, `host`, `remotePort`, `password`, `method`, `route`, `remoteDns`, `proxyApps`, `bypass`, `udpdns`, `ipv6`, `individual`, `tx`, `rx`, `userOrder`, `plugin`") {
override fun migrate(database: SupportSQLiteDatabase) {
super.migrate(database)
PublicDatabase.Migration3.migrate(database)
}
}
}
......@@ -21,11 +21,9 @@
package com.github.shadowsocks.database
import android.database.sqlite.SQLiteCantOpenDatabaseException
import android.util.Log
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.ProfilesFragment
import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.DirectBoot
import com.github.shadowsocks.utils.printLog
import java.io.IOException
import java.sql.SQLException
......@@ -34,27 +32,18 @@ import java.sql.SQLException
* to ensure we are in a consistent state.
*/
object ProfileManager {
private const val TAG = "ProfileManager"
interface Listener {
fun onAdd(profile: Profile)
fun onRemove(profileId: Long)
}
var listener: Listener? = null
@Throws(SQLException::class)
fun createProfile(p: Profile? = null): Profile {
val profile = p ?: Profile()
fun createProfile(profile: Profile = Profile()): Profile {
profile.id = 0
val oldProfile = app.currentProfile
if (oldProfile != null) {
// Copy Feature Settings from old profile
profile.route = oldProfile.route
profile.ipv6 = oldProfile.ipv6
profile.proxyApps = oldProfile.proxyApps
profile.bypass = oldProfile.bypass
profile.individual = oldProfile.individual
profile.udpdns = oldProfile.udpdns
}
val last = PrivateDatabase.profileDao.queryRaw(PrivateDatabase.profileDao.queryBuilder()
.selectRaw("MAX(userOrder)").prepareStatementString()).firstResult
if (last != null && last.size == 1 && last[0] != null) profile.userOrder = last[0].toLong() + 1
PrivateDatabase.profileDao.createOrUpdate(profile)
ProfilesFragment.instance?.profilesAdapter?.add(profile)
profile.userOrder = PrivateDatabase.profileDao.nextOrder() ?: 0
profile.id = PrivateDatabase.profileDao.create(profile)
listener?.onAdd(profile)
return profile
}
......@@ -62,42 +51,51 @@ object ProfileManager {
* Note: It's caller's responsibility to update DirectBoot profile if necessary.
*/
@Throws(SQLException::class)
fun updateProfile(profile: Profile) = PrivateDatabase.profileDao.update(profile)
fun updateProfile(profile: Profile) = check(PrivateDatabase.profileDao.update(profile) == 1)
@Throws(IOException::class)
fun getProfile(id: Int): Profile? = try {
PrivateDatabase.profileDao.queryForId(id)
fun getProfile(id: Long): Profile? = try {
PrivateDatabase.profileDao[id]
} catch (ex: SQLiteCantOpenDatabaseException) {
throw IOException(ex)
} catch (ex: SQLException) {
if (ex.cause is SQLiteCantOpenDatabaseException) throw IOException(ex)
Log.e(TAG, "getProfile", ex)
app.track(ex)
printLog(ex)
null
}
@Throws(SQLException::class)
fun delProfile(id: Int) {
PrivateDatabase.profileDao.deleteById(id)
ProfilesFragment.instance?.profilesAdapter?.removeId(id)
fun delProfile(id: Long) {
check(PrivateDatabase.profileDao.delete(id) == 1)
listener?.onRemove(id)
if (id == DataStore.profileId && DataStore.directBootAware) DirectBoot.clean()
}
@Throws(SQLException::class)
fun clear() = PrivateDatabase.profileDao.deleteAll().also {
// listener is not called since this won't be used in mobile submodule
DirectBoot.clean()
}
@Throws(IOException::class)
fun getFirstProfile(): Profile? = try {
PrivateDatabase.profileDao.query(PrivateDatabase.profileDao.queryBuilder().limit(1L).prepare()).singleOrNull()
} catch (ex: SQLException) {
if (ex.cause is SQLiteCantOpenDatabaseException) throw IOException(ex)
Log.e(TAG, "getFirstProfile", ex)
app.track(ex)
null
fun ensureNotEmpty() {
val nonEmpty = try {
PrivateDatabase.profileDao.isNotEmpty()
} catch (ex: SQLiteCantOpenDatabaseException) {
throw IOException(ex)
} catch (ex: SQLException) {
printLog(ex)
false
}
if (!nonEmpty) DataStore.profileId = createProfile().id
}
@Throws(IOException::class)
fun getAllProfiles(): List<Profile>? = try {
PrivateDatabase.profileDao.query(PrivateDatabase.profileDao.queryBuilder().orderBy("userOrder", true).prepare())
PrivateDatabase.profileDao.list()
} catch (ex: SQLiteCantOpenDatabaseException) {
throw IOException(ex)
} catch (ex: SQLException) {
if (ex.cause is SQLiteCantOpenDatabaseException) throw IOException(ex)
Log.e(TAG, "getAllProfiles", ex)
app.track(ex)
printLog(ex)
null
}
}
package com.github.shadowsocks.plugin
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.Core.app
object NoPlugin : Plugin() {
override val id: String get() = ""
override val label: CharSequence get() = app.getText(com.github.shadowsocks.R.string.plugin_disabled)
override val label: CharSequence get() = app.getText(com.github.shadowsocks.core.R.string.plugin_disabled)
}
......@@ -20,7 +20,7 @@
package com.github.shadowsocks.preference
import android.support.v7.preference.PreferenceDataStore
import androidx.preference.PreferenceDataStore
interface OnPreferenceDataStoreChangeListener {
fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String?)
......
This diff is collapsed.
This diff is collapsed.
Subproject commit 58f8a8883e51e5cb97391c4b6733ce255bf11f95
Subproject commit 9965ce070a5330602a71e97fdd41213e1fdd8d6d
This diff is collapsed.
Subproject commit c5e43f4c1cf62b4669b1f33516fc33ef2d005187
Subproject commit fec4c92d81189e0394ad94e14f238a837c81385e
Subproject commit a4c9059ffe515d86b79724a289c0772ce9f2cf09
Subproject commit 91222766e793a6d1516ac066984b63069594e0bf
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.
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.
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