Compare commits

...
95 Commits
Author SHA1 Message Date
Max Lv f0e5681052 Bump version 2018-02-25 19:38:04 +08:00
Max Lv d10a406584 Refine overture memory usage 2018-02-25 11:23:10 +08:00
Max Lv a99a07f586 Update overture 2018-02-24 20:32:45 +08:00
Mygod 39a0a61da2 Allow using TileService in direct boot mode 2018-02-22 11:56:26 -08:00
Mygod cf118a1bbf Remove unnecessary crossfader dependency 2018-02-17 20:39:24 -08:00
Mygod 510098911e Fix domain name display in TileService 2018-02-17 20:35:09 -08:00
Mygod 201bb4fe58 Remove unnecessary tint in TileService 2018-02-17 14:13:08 -08:00
Mygod 026d1ecb2b Fix receiver leaking 2018-02-15 15:34:56 -08:00
Mygod 7461d3bf30 Fix listenForPackageChanges for upgrades again 2018-02-14 00:18:49 -08:00
Mygod 4483765b2d Fix listenForPackageChanges 2018-02-14 00:00:15 -08:00
Mygod 62375a576c Enable multi-dex for debug builds
This is not a big deal thanks to min API = 21 and instant run already uses multi-dex anyways.
2018-02-11 03:07:24 -08:00
Mygod 4904e9c06c Update dependencies 2018-02-11 02:31:44 -08:00
Mygod f75b54699a Use Google mobile vision API to scan QR codes as well
Interestingly even though multiple QR code isn't enabled, if there are two QR codes in the picture they will all get scanned.
2018-02-11 02:26:23 -08:00
Mygod 22ffcc53bb Refine QR code decoding 2018-02-11 01:55:14 -08:00
Max Lv b69c20d086 Use mobile vision APIs for QR code from files 2018-02-10 03:46:47 -08:00
Max Lv c71c6ffe02 Fix a crash in QuickToggle 2018-02-05 16:58:45 -08:00
Max Lv 439d56d1d7 Remove the return value in traffic IPC 2018-02-05 01:38:45 -08:00
Mygod 81f35acf20 Let Zxing try harder 2018-02-05 00:11:50 -08:00
Mygod a755b669c8 Fix code style issues 2018-02-04 15:07:38 -08:00
Mygod 9fb180956f Add detekt.yml 2018-02-04 13:38:53 -08:00
Mygod 39530a1a3b Fix a crash if you're trying to do weird thing 2018-02-04 05:41:06 -08:00
Mygod b91d9474d5 Support importing from file
Now scanning QR codes on camera-less devices actually do sth.
2018-02-04 05:35:03 -08:00
Mygod f0140c0070 Add back scanner shortcut 2018-02-04 04:45:51 -08:00
Mygod d23be09cb0 Add snackbars everywhere 2018-02-04 04:29:54 -08:00
Mygod 4a97ff1d30 Makes less noise while stopping service 2018-02-04 04:14:39 -08:00
Mygod 88fc092aa2 Get rid of postDelayeds
FASTER!
2018-02-04 04:12:17 -08:00
Mygod d715f7b4c6 Fix another crash 2018-02-04 04:08:46 -08:00
Mygod 7cc3c48240 Fix translucent style 2018-02-04 04:08:34 -08:00
Mygod 26fb0e5e3c Prevent ANR for TFO 2018-02-04 03:35:10 -08:00
Mygod f4d0835e00 Refine error message 2018-02-04 03:16:01 -08:00
Mygod ebcf514323 Handle errors in acl dialog more gracefully 2018-02-04 03:08:01 -08:00
Mygod 3975f3f43c Disconnect as soon as binder died 2018-02-03 22:24:42 -08:00
Mygod be79e6f80c Suppress crash if platform does not support jobs
This is apparently an issue for some HTC devices.
2018-02-03 21:07:48 -08:00
Mygod 61edb40dd1 Remove unnecessary APIs 2018-02-03 21:03:59 -08:00
Mygod 0046ed87d5 Fix a crash on Android 6.0 2018-02-03 20:56:55 -08:00
initrider 4f44d119ee Update translation of FAQ (#1626)
* Create faq.ru-RU.md

* Update faq.ru-RU.md

* Translate - FAQ - Russian

* Rename .github/faq.ru-RU.md to  .github/faq.ru.md

* Fixed translation mistakes + Fix #1565

* Update faq.ru.md

* Visual improvement

* Add #888

* Add #1589 and retranslate a bit of info

* Removed unnecessary word
2018-02-02 11:31:58 -08:00
Mygod 1e5aabf824 Add #888 to FAQ 2018-01-30 20:33:13 -08:00
Mygod 7a49ddc045 Fix #1612 2018-01-28 00:09:01 -08:00
Mygod 5ced42125c Implement ActionMode for CustomRulesFragment 2018-01-27 15:25:29 -08:00
Mygod 3f88b46353 Retire tablet layout
Google doesn't care for tablets either.
2018-01-27 15:09:07 -08:00
Mygod 34da03d6e3 Dependency updates 2018-01-27 13:32:32 -08:00
Mygod 16c0d27130 Update kotlin 2018-01-25 22:33:35 -08:00
Mygod bff2615599 Add #1589 to FAQ 2018-01-25 22:17:06 -08:00
Max Lv c63a659d88 Fix remote DNS 2018-01-24 18:21:32 +08:00
Mygod cb3fa2d923 Fix gradle build script
Now that we have max(versionCodes) = 211003, it's time to just change this versionCode scheme.
2018-01-24 00:14:21 -08:00
Mygod daea4b5f6c Refine connection tests 2018-01-24 00:14:21 -08:00
Max Lv 8e7ddaebbd Add OpenDNS into the primary list 2018-01-23 23:10:01 -08:00
Max Lv aaf9f27d56 Add universal apk 2018-01-23 23:10:01 -08:00
Mygod 7898eef48d Bump versionCode 2018-01-23 22:45:12 -08:00
Mygod 71979434d7 Revert "Fix ABI versions"
This reverts commit ad522572f0.
2018-01-23 22:44:53 -08:00
Max Lv ad522572f0 Fix ABI versions 2018-01-22 21:57:23 -08:00
Max Lv d9fa4481a2 Bump version 2018-01-22 19:12:23 -08:00
Max Lv e2c29594f2 Set different timeouts 2018-01-22 19:04:00 -08:00
Max Lv 2ef042d56b Merge pull request #1593 from chon219/fix-windows-script
fixed windows build script for overture
2018-01-22 12:58:36 +08:00
Zichuan Ma 0fa97f064f fixed windows build script for overture 2018-01-21 18:57:20 +08:00
Max Lv d7ecc34836 Merge pull request #1491 from Mygod/split-abi
Split ABIs for smaller apk size
2018-01-19 15:38:44 +08:00
Mygod 168c0588a6 Fix debug builds 2018-01-18 23:10:12 -08:00
Mygod 4f5ee68f40 Add universalApk 2018-01-18 22:58:26 -08:00
Mygod 0c3352935a Merge branch 'master' into split-abi 2018-01-18 22:34:22 -08:00
Mygod 0d02a39b61 Update Application.mk
Update min api.
2018-01-18 21:54:01 -08:00
Mygod 090cc35bff Fix links in build-shared-executable 2018-01-18 20:58:15 -08:00
Max Lv 042ee55421 Merge pull request #1585 from Mygod/min21
Min API 19 -> 21
2018-01-19 12:33:15 +08:00
Mygod ac96e6fc9c Min API 19 -> 21
Fix #1579.
2018-01-18 19:57:30 -08:00
Mygod c6ff9b3d89 Add license badge 2018-01-18 11:51:10 -08:00
Mygod 77af5f6e3c Refine #1579 2018-01-18 11:46:53 -08:00
Reker 95db29bd13 Fix Windows build script of overture (#1582)
make.bat not detect Windows go binary due to missing '.exe' of filename
when the script convert from bash to bat
2018-01-18 16:12:40 +08:00
Max Lv 7297a2664c Avoid problematic local DNS resolver 2018-01-16 23:17:00 -08:00
Mygod 195e2afbeb Fix keyboard navigations 2018-01-15 22:24:08 -08:00
Mygod 3dbc5f4147 Import translations from POEditor 2018-01-15 21:32:56 -08:00
Mygod 580ce14809 Refine animations 2018-01-15 21:01:29 -08:00
Mygod d4213e8ee8 Remove checkChineseLocale
This was introduced by commit 396d31a0a7. I just did some further testing and:

* zh_rCN and zh_rTW are the only zh locales you can pick before Android 7;
* Since Android 7, user can use locale list to specify their fallback preferences and there's no need to enforce it.

Therefore this method isn't really necessary and will be thus removed.
2018-01-15 19:58:16 -08:00
Max Lv be168c77f6 Bump version 2018-01-15 02:04:31 -08:00
Mygod f9b29acd82 Update notification channels on configuration change 2018-01-14 06:22:58 +08:00
Max Lv 4e943bc782 Reduce the DNS timeout 2018-01-12 17:02:22 +08:00
Max Lv b3a818b03a Merge pull request #1570 from imReker/master
Support build overture on Windows
2018-01-12 10:18:19 +08:00
Max Lv 982845e877 Enlarge the timeout of protecting socket 2018-01-11 22:11:55 +08:00
imReker 4e35fe19c8 Support build overture on Windows 2018-01-11 18:01:08 +08:00
initrider 45d8e827c1 Fixed translation mistakes + Fix #1565 (#1568)
* Create faq.ru-RU.md

* Update faq.ru-RU.md

* Translate - FAQ - Russian

* Rename .github/faq.ru-RU.md to  .github/faq.ru.md

* Fixed translation mistakes + Fix #1565

* Update faq.ru.md

* Visual improvement
2018-01-10 09:27:26 +08:00
Max Lv 90dd0ab300 Refine local DNS resolver 2018-01-06 15:13:26 +08:00
Syrone Wong dee540b83a update gradle wrapper to 4.4.1 (#1560)
$ ./gradlew wrapper --gradle-version=4.4.1 --distribution-type=all
2018-01-05 20:50:07 +08:00
Max Lv f3b3507c0f Merge pull request #1558 from wongsyrone/update-ip
update China and Telegram ip range
2018-01-05 18:25:37 +08:00
Syrone Wong 402710841b update China and Telegram ip range 2018-01-05 17:12:04 +08:00
Max Lv 6ded9d3d37 Refine #1556 2018-01-04 17:06:56 -08:00
initrider 88274fb732 Translation of FAQ to Russian language (#1552) 2018-01-04 21:09:32 +08:00
Mygod 36e6cfbe59 Remove redundant sbt file 2018-01-04 21:08:08 +08:00
Max Lv f05761ac4d Refine the animation of snackbar 2018-01-04 19:25:31 +08:00
Mygod a33efea2b5 Add French 2018-01-04 19:03:07 +08:00
Mygod b2e6639901 Remove translatable 2018-01-04 18:23:45 +08:00
Max Lv db74988190 Fix the listview issue 2018-01-03 18:37:36 -08:00
Max Lv 9a43c78e7b Fix #1548 2018-01-03 17:21:46 -08:00
Max Lv e915a4221d Merge branch 'beta' 2018-01-02 20:05:32 -08:00
Mygod cd1f94f4a0 Change version code 2017-12-14 12:26:32 -08:00
Max Lv 040a6a2815 Bump version 2017-12-09 16:27:45 +08:00
Mygod 0b9b0a91c8 Fix #1490 2017-12-07 22:14:19 -08:00
Mygod 6dace959d2 Split ABIs for smaller apk size 2017-12-06 23:58:11 -08:00
123 changed files with 4855 additions and 2468 deletions
+5 -10
View File
@@ -3,9 +3,8 @@
Cannot connect to server:
1. Stop battery saver if it's active;
2. If an upgrade breaks anything, do a manual reset first by pressing Reset at the end of the list;
3. Check your config;
4. Wipe app data.
2. Check your config;
3. Wipe app data.
Crash: [Submit an issue](https://github.com/shadowsocks/shadowsocks-android/issues/new) with logcat attached, or submit a crash report to Google Play. Then, try wiping app data.
@@ -13,11 +12,6 @@ Crash: [Submit an issue](https://github.com/shadowsocks/shadowsocks-android/issu
Use [Tasker](http://tasker.dinglisch.net/) integration.
### How to add QR code from local gallery?
Scan it with a third-party scanner like [QuickMark Barcode Scanner](https://play.google.com/store/apps/details?id=tw.com.quickmark) and click the `ss` url.
### Why is NAT mode deprecated?
1. Requiring ROOT permission;
@@ -32,9 +26,10 @@ The exclamation mark in the Wi-Fi/cellular icon appears because the system fails
1. Some ROM has broken VPNService implementation, especially for IPv6;
2. Some ROM has aggressive (or called broken) background service killing policy;
3. If you have Xposed framework and/or battery saver apps, it's likely that this app wouldn't work well with these either.
3. Some ROM like [Flyme](https://github.com/shadowsocks/shadowsocks-android/issues/1589) has **very** broken direct boot support;
4. If you have Xposed framework and/or battery saver apps, it's likely that this app wouldn't work well with these either.
* Fixes for MIUI: [#772](https://github.com/shadowsocks/shadowsocks-android/issues/772)
* Fixes for MIUI: [#772](https://github.com/shadowsocks/shadowsocks-android/issues/772) [#888](https://github.com/shadowsocks/shadowsocks-android/issues/888)
* Fixes for Huawei: [#1091 (comment)](https://github.com/shadowsocks/shadowsocks-android/issues/1091#issuecomment-276949836)
* Related to Xposed: [#1414](https://github.com/shadowsocks/shadowsocks-android/issues/1414)
* Samsung and/or Brevent: [#1410](https://github.com/shadowsocks/shadowsocks-android/issues/1410)
+101
View File
@@ -0,0 +1,101 @@
### Решение проблем
Не получается подключиться к серверу:
1. Выключите режим энергосбережения, если он активен;
2. Проверьте настройки;
3. Сотрите данные приложения.
Не работает: [Добавьте отчет](https://github.com/shadowsocks/shadowsocks-android/issues/new) прикрепив лог (logcat), или отправьте отзыв в Google Play. Затем попробуйте стереть данные приложения.
### Как добавить виджет и/или переключать профиль на основании состояния сети?
Использйте интеграцию с [Tasker](http://tasker.dinglisch.net/).
### Как добавить QR код из локальной галереи?
Отсканируйте его при помощи стороннего сканера, например [QuickMark Barcode Scanner](https://play.google.com/store/apps/details?id=tw.com.quickmark) и нажмите на `ss` ссылку.
### Почему режим NAT более не поддерживается?
1. Требуются ROOT права;
2. Нет поддержки IPv6;
3. Нет поддержки UDP relay.
### Как убрать восклицательный знак когда используется режим VPN?
Восклицательный знак на значке Wi-Fi/Сотовой связи появляется, потому что система не может подключиться к portal-серверу (по-умолчанию к `clients3.google.com`) без VPN подключения. Чтобы убрать его, следуйте инструкциям в [этом топике](https://www.noisyfox.cn/45.html). (на упрощенном китайском)
### Почему мой ROM не поддерживается?
1. Некоторые ROM используют неправильную реализацию службы VPN, особенно IPv6;
2. Некоторые ROM используют агрессивную (или так называемую испорченную) фоновую службу с ошибочной политикой остановки;
3. Некоторые ROM, такие как [Flyme](https://github.com/shadowsocks/shadowsocks-android/issues/1589) имеют **слишком** испорченную поддержку "непосредственной загрузки";
4. Если вы используете фреймворк Xposed и/или приложения для энергосбережения, вероятно наше приложение не будет нормально работать с ними.
* Иправления для MIUI: [#772](https://github.com/shadowsocks/shadowsocks-android/issues/772) [#888](https://github.com/shadowsocks/shadowsocks-android/issues/888)
* Иправления для Huawei: [#1091 (комментарий)](https://github.com/shadowsocks/shadowsocks-android/issues/1091#issuecomment-276949836)
* Вызванные из-за Xposed: [#1414](https://github.com/shadowsocks/shadowsocks-android/issues/1414)
* Samsung и/или Brevent: [#1410](https://github.com/shadowsocks/shadowsocks-android/issues/1410)
* Не устанавливайте это приложение на SD карту, из-за проблем с разрешениями: [#1124 (комментарий)](https://github.com/shadowsocks/shadowsocks-android/issues/1124#issuecomment-307556453)
### Как приостановить службу Shadowsocks?
* Для Android 7.0+: Используйте переключатель в быстрых настройках;
* Используйте интеграцию с Tasker;
* Добавьте профиль c выбранными приложениями, которые будут работать через Shadowsocks прокси, выключив обходной режим.
### Почему Shadowsocks потребляет так много энергии на Android 5.0+?
Так как Shadowsocks работает со всем траффиком устройства, заряд батареи используемый другими приложениями также считается за тот, что использует Shadowsocks. Таким образом использование батареи Shadowsocks'ом равен сумме всей активности устройства в сети. Shadowsocks сам по себе в общем-то является приложением связанного процесса ввода/вывода на современных Android устройствах, которое, ожидаемо, не потребляет значительного количества энергии.
Так что если вы заметите значительное увеличение потребляемой энергии после того, как вы использовали Shadowsocks, это скорее всего вызванно другими приложениями. Например, сервисы Google Play могут потреблять больше энергии после того, как подключатся к Google, и т.д.
Больше информации: https://kb.adguard.com/en/android/solving-problems/battery
### Оно прекрасно работает при Wi-Fi но не может подключиться через сотовую связь?
Разрешите этому приложению передачу данных в фоновом режиме в настройках приложения.
Если у вас оперератор Yota, то измените точку доступа APN с `yota.ru` на `client.yota.ru`
### Почему требуется разрешение камере на устройствах младше Android 6.0?
Чтобы сканировать QR код через встроенный QR сканер.
Кстати, обновите уже свою версию Android.
### Как использовать режим Transproxy (прозрачный прокси)?
1. Установите [AFWall+](https://github.com/ukanth/afwall);
2. Настройте пользовательский скрипт:
```sh
IP6TABLES=/system/bin/ip6tables
IPTABLES=/system/bin/iptables
ULIMIT=/system/bin/ulimit
SHADOWSOCKS_UID=`dumpsys package com.github.shadowsocks | grep userId | cut -d= -f2 - | cut -d' ' -f1 -`
PORT_DNS=5450
PORT_TRANSPROXY=8200
$ULIMIT -n 4096
$IP6TABLES -F
$IP6TABLES -A INPUT -j DROP
$IP6TABLES -A OUTPUT -j DROP
$IPTABLES -t nat -F OUTPUT
$IPTABLES -t nat -A OUTPUT -o lo -j RETURN
$IPTABLES -t nat -A OUTPUT -d 127.0.0.1 -j RETURN
$IPTABLES -t nat -A OUTPUT -m owner --uid-owner $SHADOWSOCKS_UID -j RETURN
$IPTABLES -t nat -A OUTPUT -p tcp --dport 53 -j DNAT --to-destination 127.0.0.1:$PORT_DNS
$IPTABLES -t nat -A OUTPUT -p udp --dport 53 -j DNAT --to-destination 127.0.0.1:$PORT_DNS
$IPTABLES -t nat -A OUTPUT -p tcp -j DNAT --to-destination 127.0.0.1:$PORT_TRANSPROXY
$IPTABLES -t nat -A OUTPUT -p udp -j DNAT --to-destination 127.0.0.1:$PORT_TRANSPROXY
```
3. Настройте пользовательский скрипт отключения:
```sh
IP6TABLES=/system/bin/ip6tables
IPTABLES=/system/bin/iptables
$IPTABLES -t nat -F OUTPUT
$IP6TABLES -F
```
4. Убедитесь, что траффик для Shadowsocks разрешен;
5. Запустите службу прозрачного прокси (transproxy) в Shadowsocks и включите брандмауэр.
+3
View File
@@ -24,3 +24,6 @@ core/src/overture/src/gopkg.in
# work in progress
tv/
things/
# release apks
*.apk
+3
View File
@@ -1,7 +1,10 @@
## Shadowsocks for Android
[![Build Status](https://api.travis-ci.org/shadowsocks/shadowsocks-android.svg)](https://travis-ci.org/shadowsocks/shadowsocks-android)
[![API](https://img.shields.io/badge/API-21%2B-brightgreen.svg?style=flat)](https://android-arsenal.com/api?level=21)
[![Releases](https://img.shields.io/github/downloads/shadowsocks/shadowsocks-android/total.svg)](https://github.com/shadowsocks/shadowsocks-android/releases)
[![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)
[![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
A [shadowsocks](http://shadowsocks.org) client for Android, written in Kotlin.
<a href="https://play.google.com/store/apps/details?id=com.github.shadowsocks"><img src="https://play.google.com/intl/en_us/badges/images/generic/en-play-badge.png" height="48"></a>
+6 -3
View File
@@ -1,9 +1,11 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
apply plugin: 'com.github.ben-manes.versions'
buildscript {
ext {
kotlinVersion = '1.2.10'
minSdkVersion = 19
kotlinVersion = '1.2.21'
minSdkVersion = 21
sdkVersion = 27
buildToolsVersion = '27.0.3'
supportLibraryVersion = '27.0.2'
@@ -21,7 +23,8 @@ buildscript {
dependencies {
classpath 'com.android.tools.build:gradle:3.0.1'
classpath 'com.google.gms:google-services:3.1.1'
classpath 'com.github.ben-manes:gradle-versions-plugin:0.17.0'
classpath 'com.google.gms:google-services:3.2.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
}
}
+15 -6
View File
@@ -36,19 +36,28 @@ android {
}
task goBuild(type: Exec) {
executable "sh"
args "-c", "src/overture/make.bash " + minSdkVersion
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
executable "cmd.exe"
args "/c", "src\\overture\\make.bat " + minSdkVersion
} else {
executable "sh"
args "-c", "src/overture/make.bash " + minSdkVersion
}
}
task goClean(type: Exec) {
executable "sh"
args "-c", "src/overture/clean.bash"
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
executable "cmd.exe"
args "/c", "src\\overture\\clean.bat"
} else {
executable "sh"
args "-c", "src/overture/clean.bash"
}
}
tasks.whenTaskAdded { task ->
if ((task.name == 'generateJsonModelDebug' ||
task.name == 'generateJsonModelRelease') &&
!Os.isFamily(Os.FAMILY_WINDOWS)) {
task.name == 'generateJsonModelRelease')) {
task.dependsOn(goBuild)
}
}
-2
View File
@@ -1,8 +1,6 @@
#!/usr/bin/python
# -*- encoding: utf8 -*-
import itertools
import math
import sys
import IPy
@@ -43,8 +43,6 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.system.ErrnoException;
import java.net.InetAddress;
public class JniHelper {
static {
System.loadLibrary("jni-helper");
@@ -54,8 +52,7 @@ public class JniHelper {
public static void sigtermCompat(@NonNull Process process) throws Exception {
if (Build.VERSION.SDK_INT >= 24) throw new UnsupportedOperationException("Never call this method in OpenJDK!");
int errno = sigterm(process);
if (errno != 0) throw Build.VERSION.SDK_INT >= 21
? new ErrnoException("kill", errno) : new Exception("kill failed: " + errno);
if (errno != 0) throw new ErrnoException("kill", errno);
}
@Deprecated // only implemented for before API 24
+1 -1
View File
@@ -1,4 +1,4 @@
APP_ABI := armeabi-v7a arm64-v8a x86
APP_PLATFORM := android-19
APP_PLATFORM := android-21
APP_STL := c++_static
NDK_TOOLCHAIN_VERSION := clang
+2 -2
View File
@@ -16,8 +16,8 @@
# executable program
#
# Modified by @Mygod, based on:
# https://android.googlesource.com/platform/ndk/+/f2e98f8c066aed59caf61163d4b87c2b858f9814/build/core/build-shared-library.mk
# https://android.googlesource.com/platform/ndk/+/f2e98f8c066aed59caf61163d4b87c2b858f9814/build/core/build-executable.mk
# https://android.googlesource.com/platform/ndk/+/a355a4e/build/core/build-shared-library.mk
# https://android.googlesource.com/platform/ndk/+/a355a4e/build/core/build-executable.mk
LOCAL_BUILD_SCRIPT := BUILD_EXECUTABLE
LOCAL_MAKEFILE := $(local-makefile)
$(call check-defined-LOCAL_MODULE,$(LOCAL_BUILD_SCRIPT))
+10
View File
@@ -0,0 +1,10 @@
@ECHO OFF
SET DIR=%~dp0
SET DEPS=%DIR%\.deps
RD /S /Q %DEPS%
RD /S /Q %DIR%\go\bin
RD /S /Q %DIR%\bin
ECHO "Successfully clean overture"
+2 -2
View File
@@ -12,7 +12,7 @@ TARGET=$DIR/bin
DEPS=$DIR/.deps
ANDROID_ARM_TOOLCHAIN=$DEPS/android-toolchain-${MIN_API}-arm
ANDROID_ARM64_TOOLCHAIN=$DEPS/android-toolchain-21-arm64
ANDROID_ARM64_TOOLCHAIN=$DEPS/android-toolchain-${MIN_API}-arm64
ANDROID_X86_TOOLCHAIN=$DEPS/android-toolchain-${MIN_API}-x86
ANDROID_ARM_CC=$ANDROID_ARM_TOOLCHAIN/bin/arm-linux-androideabi-clang
@@ -35,7 +35,7 @@ fi
if [ ! -f "$ANDROID_ARM64_CC" ]; then
echo "Make standalone toolchain for ARM64 arch"
$ANDROID_NDK_HOME/build/tools/make_standalone_toolchain.py --arch arm64 \
--api 21 --install-dir $ANDROID_ARM64_TOOLCHAIN
--api $MIN_API --install-dir $ANDROID_ARM64_TOOLCHAIN
fi
if [ ! -f "$ANDROID_X86_CC" ]; then
+121
View File
@@ -0,0 +1,121 @@
@ECHO OFF
SETLOCAL
IF NOT DEFINED ANDROID_NDK_HOME (
SET ANDROID_NDK_HOME=%ANDROID_HOME%\ndk-bundle
)
SET DIR=%~dp0
SET MIN_API=%1%
SET TARGET=%DIR%\bin
SET DEPS=%DIR%\.deps
SET ANDROID_ARM_TOOLCHAIN=%DEPS%\android-toolchain-%MIN_API%-arm
SET ANDROID_ARM64_TOOLCHAIN=%DEPS%\android-toolchain-%MIN_API%-arm64
SET ANDROID_X86_TOOLCHAIN=%DEPS%\android-toolchain-%MIN_API%-x86
SET ANDROID_ARM_CC=%ANDROID_ARM_TOOLCHAIN%\bin\arm-linux-androideabi-clang
SET ANDROID_ARM_STRIP=%ANDROID_ARM_TOOLCHAIN%\bin\arm-linux-androideabi-strip
SET ANDROID_ARM64_CC=%ANDROID_ARM64_TOOLCHAIN%\bin\aarch64-linux-android-clang
SET ANDROID_ARM64_STRIP=%ANDROID_ARM64_TOOLCHAIN%\bin\aarch64-linux-android-strip
SET ANDROID_X86_CC=%ANDROID_X86_TOOLCHAIN%\bin\i686-linux-android-clang
SET ANDROID_X86_STRIP=%ANDROID_X86_TOOLCHAIN%\bin\i686-linux-android-strip
MKDIR %DEPS%>nul 2>nul
MKDIR %TARGET%\armeabi-v7a>nul 2>nul
MKDIR %TARGET%\x86>nul 2>nul
MKDIR %TARGET%\arm64-v8a>nul 2>nul
IF NOT EXIST %ANDROID_ARM_CC% (
ECHO "Make standalone toolchain for ARM arch"
%ANDROID_NDK_HOME%\build\tools\make_standalone_toolchain.py --arch arm ^
--api %MIN_API% --install-dir %ANDROID_ARM_TOOLCHAIN%
)
IF NOT EXIST %ANDROID_ARM64_CC% (
ECHO "Make standalone toolchain for ARM64 arch"
%ANDROID_NDK_HOME%\build\tools\make_standalone_toolchain.py --arch arm64 ^
--api %MIN_API% --install-dir %ANDROID_ARM64_TOOLCHAIN%
)
IF NOT EXIST %ANDROID_X86_CC% (
ECHO "Make standalone toolchain for X86 arch"
%ANDROID_NDK_HOME%\build\tools\make_standalone_toolchain.py --arch x86 ^
--api %MIN_API% --install-dir %ANDROID_X86_TOOLCHAIN%
)
IF NOT EXIST %DIR%\go\bin\go.exe (
ECHO "Build the custom go"
PUSHD %DIR%\go\src
CALL make.bat
POPD
)
SET GOROOT=%DIR%\go
SET GOPATH=%DIR%
SET PATH=%GOROOT%\bin;%GOPATH%\bin;%PATH%
SET BUILD=1
IF EXIST "%TARGET%\armeabi-v7a\liboverture.so" (
IF EXIST "%TARGET%\arm64-v8a\liboverture.so" (
IF EXIST "%TARGET%\x86\liboverture.so" (
SET BUILD=0
)
)
)
IF %BUILD% == 1 (
ECHO "Get dependences for overture"
go.exe get -u github.com\tools\godep
PUSHD %GOPATH%\src\github.com\shadowsocks\overture\main
godep.exe restore
ECHO "Cross compile overture for arm"
IF NOT EXIST "%TARGET%\armeabi-v7a\liboverture.so" (
SETLOCAL
SET CGO_ENABLED=1
SET CC=%ANDROID_ARM_CC%
SET GOOS=android
SET GOARCH=arm
SET GOARM=7
go.exe build -ldflags="-s -w"
%ANDROID_ARM_STRIP% main
MOVE main %TARGET%\armeabi-v7a\liboverture.so>nul 2>nul
ENDLOCAL
)
ECHO "Cross compile overture for arm64"
IF NOT EXIST "%TARGET%\arm64-v8a\liboverture.so" (
SETLOCAL
SET CGO_ENABLED=1
SET CC=%ANDROID_ARM64_CC%
SET GOOS=android
SET GOARCH=arm64
go.exe build -ldflags="-s -w"
%ANDROID_ARM64_STRIP% main
MOVE main %TARGET%\arm64-v8a\liboverture.so>nul 2>nul
ENDLOCAL
)
ECHO "Cross compile overture for x86"
IF NOT EXIST "%TARGET%\x86\liboverture.so" (
SETLOCAL
SET CGO_ENABLED=1
SET CC=%ANDROID_X86_CC%
SET GOOS=android
SET GOARCH=386
go.exe build -ldflags="-s -w"
%ANDROID_X86_STRIP% main
MOVE main %TARGET%\x86\liboverture.so>nul 2>nul
ENDLOCAL
)
POPD
)
ECHO "Successfully build overture"
ENDLOCAL
+286
View File
@@ -0,0 +1,286 @@
# https://github.com/arturbosch/detekt/blob/801994bd60cc759b181649356cea045b8125301b/detekt-cli/src/main/resources/default-detekt-config.yml
comments:
active: false
complexity:
active: true
ComplexCondition:
active: true
threshold: 3
ComplexInterface:
active: true
threshold: 10
includeStaticDeclarations: false
ComplexMethod:
active: true
threshold: 10
LabeledExpression:
active: true
LargeClass:
active: true
threshold: 150
LongMethod:
active: true
threshold: 20
LongParameterList:
active: true
threshold: 5
ignoreDefaultParameters: true
MethodOverloading:
active: false
NestedBlockDepth:
active: true
threshold: 3
StringLiteralDuplication:
active: true
threshold: 2
ignoreAnnotation: true
excludeStringsWithLessThan5Characters: true
ignoreStringsRegex: '$^'
TooManyFunctions:
active: true
thresholdInFiles: 10
thresholdInClasses: 10
thresholdInInterfaces: 10
thresholdInObjects: 10
thresholdInEnums: 10
empty-blocks:
active: true
EmptyCatchBlock:
active: false
EmptyClassBlock:
active: true
EmptyDefaultConstructor:
active: true
EmptyDoWhileBlock:
active: true
EmptyElseBlock:
active: true
EmptyFinallyBlock:
active: true
EmptyForBlock:
active: true
EmptyFunctionBlock:
active: true
EmptyIfBlock:
active: true
EmptyInitBlock:
active: true
EmptyKtFile:
active: true
EmptySecondaryConstructor:
active: true
EmptyWhenBlock:
active: true
EmptyWhileBlock:
active: true
exceptions:
active: true
ExceptionRaisedInUnexpectedLocation:
active: true
methodNames: 'toString,hashCode,equals,finalize'
InstanceOfCheckForException:
active: true
NotImplementedDeclaration:
active: true
PrintStackTrace:
active: false
RethrowCaughtException:
active: true
ReturnFromFinally:
active: true
SwallowedException:
active: true
ThrowingExceptionFromFinally:
active: true
ThrowingExceptionInMain:
active: true
ThrowingExceptionsWithoutMessageOrCause:
active: true
exceptions: 'IllegalArgumentException,IllegalStateException,IOException'
ThrowingNewInstanceOfSameException:
active: true
TooGenericExceptionCaught:
active: true
exceptions:
- ArrayIndexOutOfBoundsException
- Error
- Exception
- IllegalMonitorStateException
- NullPointerException
- IndexOutOfBoundsException
- RuntimeException
- Throwable
TooGenericExceptionThrown:
active: true
exceptions:
- Error
- Exception
- NullPointerException
- Throwable
- RuntimeException
naming:
active: true
ClassNaming:
active: true
classPattern: '[A-Z$][a-zA-Z0-9$]*'
EnumNaming:
active: true
enumEntryPattern: '^[A-Z][_a-zA-Z0-9]*'
ForbiddenClassName:
active: true
forbiddenName: ''
FunctionMaxLength:
active: true
maximumFunctionNameLength: 30
FunctionMinLength:
active: true
minimumFunctionNameLength: 3
FunctionNaming:
active: true
functionPattern: '^([a-z$][a-zA-Z$0-9]*)|(`.*`)$'
MatchingDeclarationName:
active: true
MemberNameEqualsClassName:
active: false
ObjectPropertyNaming:
active: true
propertyPattern: '[A-Za-z][_A-Za-z0-9]*'
PackageNaming:
active: true
packagePattern: '^[a-z]+(\.[a-z][a-z0-9]*)*$'
TopLevelPropertyNaming:
active: true
constantPattern: '[A-Z][_A-Z0-9]*'
propertyPattern: '[a-z][A-Za-z\d]*'
privatePropertyPattern: '(_)?[a-z][A-Za-z0-9]*'
VariableMaxLength:
active: true
maximumVariableNameLength: 64
VariableMinLength:
active: false
VariableNaming:
active: true
variablePattern: '[a-z][A-Za-z0-9]*'
privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*'
performance:
active: true
ForEachOnRange:
active: true
SpreadOperator:
active: true
UnnecessaryTemporaryInstantiation:
active: true
potential-bugs:
active: true
DuplicateCaseInWhenExpression:
active: true
EqualsAlwaysReturnsTrueOrFalse:
active: true
EqualsWithHashCodeExist:
active: true
ExplicitGarbageCollectionCall:
active: true
InvalidRange:
active: true
IteratorHasNextCallsNextMethod:
active: true
IteratorNotThrowingNoSuchElementException:
active: true
LateinitUsage:
active: false
UnconditionalJumpStatementInLoop:
active: true
UnreachableCode:
active: true
UnsafeCallOnNullableType:
active: false
UnsafeCast:
active: false
UselessPostfixExpression:
active: true
WrongEqualsTypeParameter:
active: true
style:
active: true
CollapsibleIfStatements:
active: true
DataClassContainsFunctions:
active: true
conversionFunctionPrefix: 'to'
EqualsNullCall:
active: true
ExpressionBodySyntax:
active: true
ForbiddenComment:
active: true
values: 'TODO:,FIXME:,STOPSHIP:'
ForbiddenImport:
active: true
imports: ''
FunctionOnlyReturningConstant:
active: true
ignoreOverridableFunction: true
excludedFunctions: 'describeContents'
LoopWithTooManyJumpStatements:
active: true
maxJumpCount: 1
MagicNumber:
active: false
MaxLineLength:
active: true
maxLineLength: 120
excludePackageStatements: false
excludeImportStatements: false
ModifierOrder:
active: true
NestedClassesVisibility:
active: true
NewLineAtEndOfFile:
active: true
OptionalAbstractKeyword:
active: true
OptionalReturnKeyword:
active: true
OptionalUnit:
active: true
OptionalWhenBraces:
active: true
ProtectedMemberInFinalClass:
active: true
RedundantVisibilityModifierRule:
active: true
ReturnCount:
active: false
SafeCast:
active: true
SerialVersionUIDInSerializableClass:
active: true
SpacingBetweenPackageAndImports:
active: true
ThrowsCount:
active: true
max: 2
UnnecessaryAbstractClass:
active: true
UnnecessaryInheritance:
active: true
UnnecessaryParentheses:
active: true
UntilInsteadOfRangeTo:
active: true
UnusedImports:
active: true
UseDataClass:
active: false
UtilityClassWithPublicConstructor:
active: true
WildcardImport:
active: false
Binary file not shown.
+1 -2
View File
@@ -1,6 +1,5 @@
#Fri Dec 01 17:03:17 PST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4.1-all.zip
Vendored
+56 -44
View File
@@ -1,4 +1,4 @@
#!/usr/bin/env bash
#!/usr/bin/env sh
##############################################################################
##
@@ -6,42 +6,6 @@
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
@@ -60,6 +24,46 @@ cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
@@ -85,7 +89,7 @@ location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
@@ -150,11 +154,19 @@ if $cygwin ; then
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
APP_ARGS=$(save "$@")
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"
Vendored
+4 -10
View File
@@ -8,14 +8,14 @@
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
@@ -46,10 +46,9 @@ echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
@@ -60,11 +59,6 @@ set _SKIP=2
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
+27 -13
View File
@@ -1,3 +1,4 @@
import com.android.build.OutputFile
import java.util.regex.Matcher
import java.util.regex.Pattern
@@ -7,7 +8,7 @@ apply plugin: 'kotlin-android'
def getCurrentFlavor() {
String task = getGradle().getStartParameter().getTaskRequests().toString()
Matcher matcher = Pattern.compile("(assemble|generate)\\w*(Release|Debug)").matcher(task)
if (matcher.find()) return matcher.group(1).toLowerCase() else {
if (matcher.find()) return matcher.group(2).toLowerCase() else {
println "Warning: No match found for $task"
return "debug"
}
@@ -20,15 +21,15 @@ android {
applicationId "com.github.shadowsocks"
minSdkVersion rootProject.minSdkVersion
targetSdkVersion rootProject.sdkVersion
versionCode 203
versionName "4.4.3"
versionCode 4050000
versionName "4.5.0"
testApplicationId "com.github.shadowsocks.test"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary true
resConfigs "fa", "ja", "ko", "ru", "zh-rCN", "zh-rTW"
resConfigs "fa", "fr", "ja", "ko", "ru", "zh-rCN", "zh-rTW"
}
buildTypes {
debug {
multiDexEnabled true
pseudoLocalesEnabled true
}
release {
@@ -37,6 +38,15 @@ android {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
lintOptions.checkReleaseBuilds false
splits {
abi {
enable true
reset()
include 'armeabi-v7a', 'arm64-v8a', 'x86'
universalApk true
}
}
sourceSets.main.jniLibs.srcDirs +=
new File(project(':core').buildDir, "intermediates/bundles/${getCurrentFlavor()}/jni")
sourceSets.main.jniLibs.srcDirs += new File(project(':core').projectDir, "src/overture/bin")
@@ -48,25 +58,21 @@ dependencies {
implementation "com.android.support:design:$supportLibraryVersion"
implementation "com.android.support:gridlayout-v7:$supportLibraryVersion"
implementation 'com.futuremind.recyclerfastscroll:fastscroll:0.2.5'
implementation 'com.evernote:android-job:1.2.1'
implementation 'com.evernote:android-job:1.2.4'
implementation "com.google.android.gms:play-services-ads:$playServicesVersion"
implementation "com.google.android.gms:play-services-analytics:$playServicesVersion"
implementation "com.google.android.gms:play-services-gcm:$playServicesVersion"
implementation "com.google.android.gms:play-services-vision:$playServicesVersion"
implementation "com.google.firebase:firebase-config:$playServicesVersion"
implementation 'com.j256.ormlite:ormlite-android:5.0'
implementation 'com.mikepenz:crossfader:1.5.1'
implementation 'com.mikepenz:fastadapter:3.0.4'
implementation 'com.mikepenz:iconics-core:3.0.0'
implementation 'com.mikepenz:materialdrawer:6.0.2'
implementation 'com.mikepenz:materialize:1.1.2'
implementation 'com.mikepenz:materialdrawer:6.0.6'
implementation 'com.squareup.okhttp3:okhttp:3.9.1'
implementation "com.takisoft.fix:preference-v7-simplemenu:$takisoftFixVersion"
implementation 'com.twofortyfouram:android-plugin-api-for-locale:1.0.2'
implementation 'dnsjava:dnsjava:2.1.8'
implementation 'eu.chainfire:libsuperuser:1.0.0.201704021214'
implementation 'me.dm7.barcodescanner:zxing:1.9.8'
implementation 'net.glxn.qrgen:android:2.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion"
implementation 'xyz.belvi.mobilevision:barcodescanner:2.0.3'
testImplementation "junit:junit:$junitVersion"
androidTestImplementation "com.android.support.test:runner:$androidTestVersion"
androidTestImplementation "com.android.support.test.espresso:espresso-core:$androidEspressoVersion"
@@ -76,3 +82,11 @@ repositories {
}
apply plugin: 'com.google.gms.google-services'
ext.abiCodes = ['armeabi-v7a': 1, 'arm64-v8a': 2, x86: 3]
if (getCurrentFlavor() == 'release') android.applicationVariants.all { variant ->
variant.outputs.each { output ->
def offset = project.ext.abiCodes.get(output.getFilter(OutputFile.ABI))
if (offset != null) output.versionCodeOverride = variant.versionCode + offset
}
}
+6 -7
View File
@@ -71,7 +71,7 @@
<activity
android:name=".VpnRequestActivity"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
android:theme="@style/Theme.Translucent"
android:excludeFromRecents="true"
android:taskAffinity=""
android:launchMode="singleTask"/>
@@ -169,17 +169,16 @@
android:process=":bg"
android:directBootAware="true"/>
<service android:name="com.evernote.android.job.v14.PlatformAlarmService"
android:process=":bg"
android:directBootAware="true"/>
tools:node="remove"/>
<service android:name="com.evernote.android.job.v14.PlatformAlarmServiceExact"
tools:node="remove"/>
<service android:name="com.evernote.android.job.gcm.PlatformGcmService"
android:process=":bg"
android:directBootAware="true"/>
tools:node="remove"/>
<service android:name="com.evernote.android.job.JobRescheduleService"
android:process=":bg"
android:directBootAware="true"/>
<receiver android:name="com.evernote.android.job.v14.PlatformAlarmReceiver"
android:process=":bg"
android:directBootAware="true"/>
tools:node="remove"/>
<receiver android:name="com.evernote.android.job.JobBootReceiver"
android:process=":bg"
android:directBootAware="true"/>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,3 +1,6 @@
[proxy_all]
[bypass_list]
0.0.0.0/8
10.0.0.0/8
100.64.0.0/10
File diff suppressed because it is too large Load Diff
+5 -2
View File
@@ -4,10 +4,13 @@
[proxy_list]
# Telegram IPs$
91.108.4.0/22
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
109.239.140.0/24
149.154.160.0/20
(^|\.)4tern\.com$
(^|\.)adorama\.com$
(^|\.)akiba-web\.com$
File diff suppressed because it is too large Load Diff
@@ -32,14 +32,15 @@ import android.content.pm.PackageManager
import android.content.res.Configuration
import android.os.Build
import android.os.Handler
import android.os.LocaleList
import android.os.Looper
import android.support.annotation.RequiresApi
import android.support.v4.os.UserManagerCompat
import android.support.v7.app.AppCompatDelegate
import android.util.Log
import android.widget.Toast
import com.evernote.android.job.JobConstants
import com.evernote.android.job.JobManager
import com.evernote.android.job.JobManagerCreateException
import com.github.shadowsocks.acl.Acl
import com.github.shadowsocks.acl.AclSyncJob
import com.github.shadowsocks.bg.BaseService
@@ -59,20 +60,11 @@ import com.j256.ormlite.logger.LocalLog
import com.takisoft.fix.support.v7.preference.PreferenceFragmentCompat
import java.io.File
import java.io.IOException
import java.util.*
class App : Application() {
companion object {
lateinit var app: App
private const val TAG = "ShadowsocksApplication"
// The ones in Locale doesn't have script included
private val SIMPLIFIED_CHINESE by lazy {
if (Build.VERSION.SDK_INT >= 21) Locale.forLanguageTag("zh-Hans-CN") else Locale.SIMPLIFIED_CHINESE
}
private val TRADITIONAL_CHINESE by lazy {
if (Build.VERSION.SDK_INT >= 21) Locale.forLanguageTag("zh-Hant-TW") else Locale.TRADITIONAL_CHINESE
}
}
val handler by lazy { Handler(Looper.getMainLooper()) }
@@ -112,59 +104,11 @@ class App : Application() {
t.printStackTrace()
}
private fun checkChineseLocale(config: Configuration) {
fun check(locale: Locale): Locale? {
if (locale.language != "zh") return null
when (locale.country) { "CN", "TW" -> return null }
if (Build.VERSION.SDK_INT >= 21) when (locale.script) {
"Hans" -> return SIMPLIFIED_CHINESE
"Hant" -> return TRADITIONAL_CHINESE
else -> Log.w(TAG, "Unknown zh locale script: ${locale.script}. Falling back to trying countries...")
}
when (locale.country) {
"SG" -> return SIMPLIFIED_CHINESE
"HK", "MO" -> return TRADITIONAL_CHINESE
}
Log.w(TAG, "Unknown zh locale: %s. Falling back to zh-Hans-CN..."
.format(Locale.ENGLISH, if (Build.VERSION.SDK_INT >= 21) locale.toLanguageTag() else locale))
return SIMPLIFIED_CHINESE
}
if (Build.VERSION.SDK_INT >= 24) @RequiresApi(24) {
val localeList = config.locales
var changed = false
val newList = Array<Locale>(localeList.size(), { i ->
val locale = localeList[i]
val newLocale = check(locale)
if (newLocale == null) locale else {
changed = true
newLocale
}
})
if (changed) {
val newConfig = Configuration(config)
newConfig.locales = LocaleList(*(newList.distinct().toTypedArray()))
val res = resources
res.updateConfiguration(newConfig, res.displayMetrics)
}
} else {
@Suppress("DEPRECATION")
val newLocale = check(config.locale)
if (newLocale != null) {
val newConfig = Configuration(config)
@Suppress("DEPRECATION")
newConfig.locale = newLocale
val res = resources
res.updateConfiguration(newConfig, res.displayMetrics)
}
}
}
override fun onCreate() {
super.onCreate()
app = this
if (!BuildConfig.DEBUG) System.setProperty(LocalLog.LOCAL_LOG_LEVEL_PROPERTY, "ERROR")
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
checkChineseLocale(resources.configuration)
PreferenceFragmentCompat.registerPreferenceFragment(IconListPreference::class.java,
BottomSheetPreferenceDialogFragment::class.java)
@@ -184,11 +128,16 @@ class App : Application() {
remoteConfig.fetch().addOnCompleteListener {
if (it.isSuccessful) remoteConfig.activateFetched() else Log.e(TAG, "Failed to fetch config")
}
JobManager.create(deviceContext).addJobCreator(AclSyncJob)
try {
JobManager.create(deviceContext).addJobCreator(AclSyncJob)
} catch (e: JobManagerCreateException) {
Toast.makeText(this, e.message, Toast.LENGTH_SHORT).show()
app.track(e)
}
// handle data restored
if (DataStore.directBootAware && UserManagerCompat.isUserUnlocked(this)) DirectBoot.update()
TcpFastOpen.enabled(DataStore.publicStore.getBoolean(Key.tfo, TcpFastOpen.sendEnabled))
TcpFastOpen.enabledAsync(DataStore.publicStore.getBoolean(Key.tfo, TcpFastOpen.sendEnabled))
if (DataStore.publicStore.getLong(Key.assetUpdateTime, -1) != info.lastUpdateTime) {
val assetManager = assets
for (dir in arrayOf("acl", "overture"))
@@ -203,6 +152,15 @@ class App : Application() {
DataStore.publicStore.putLong(Key.assetUpdateTime, info.lastUpdateTime)
}
updateNotificationChannels()
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
updateNotificationChannels()
}
private fun updateNotificationChannels() {
if (Build.VERSION.SDK_INT >= 26) @RequiresApi(26) {
val nm = getSystemService(NotificationManager::class.java)
nm.createNotificationChannels(listOf(
@@ -216,18 +174,16 @@ class App : Application() {
}
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
checkChineseLocale(newConfig)
}
fun listenForPackageChanges(callback: () -> Unit): BroadcastReceiver {
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 = broadcastReceiver { _, intent ->
if (intent.action != Intent.ACTION_PACKAGE_REMOVED ||
!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) callback()
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
@@ -34,6 +34,7 @@ import android.content.pm.PackageManager
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.os.Handler
import android.support.design.widget.Snackbar
import android.support.v4.app.TaskStackBuilder
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.DefaultItemAnimator
@@ -43,7 +44,6 @@ import android.support.v7.widget.Toolbar
import android.view.*
import android.widget.ImageView
import android.widget.Switch
import android.widget.Toast
import com.futuremind.recyclerviewfastscroll.FastScroller
import com.futuremind.recyclerviewfastscroll.SectionTitleProvider
import com.github.shadowsocks.App.Companion.app
@@ -63,11 +63,14 @@ class AppManager : AppCompatActivity(), Toolbar.OnMenuItemClickListener {
private var receiver: BroadcastReceiver? = null
private var cachedApps: List<PackageInfo>? = null
private fun getApps(pm: PackageManager): List<ProxiedApp> {
if (receiver == null) receiver = app.listenForPackageChanges {
synchronized(AppManager) { cachedApps = null }
AppManager.instance?.reloadApps()
}
return synchronized(AppManager) {
if (receiver == null) receiver = app.listenForPackageChanges {
synchronized(AppManager) {
receiver = null
cachedApps = null
}
AppManager.instance?.reloadApps()
}
// Labels and icons can change on configuration (locale, etc.) changes, therefore they are not cached.
val cachedApps = cachedApps ?: pm.getInstalledPackages(PackageManager.GET_PERMISSIONS)
.filter { it.packageName != app.packageName &&
@@ -231,14 +234,14 @@ class AppManager : AppCompatActivity(), Toolbar.OnMenuItemClickListener {
ProfileManager.updateProfile(it)
}
if (DataStore.directBootAware) DirectBoot.update()
Toast.makeText(this, R.string.action_apply_all, Toast.LENGTH_SHORT).show()
} else Toast.makeText(this, R.string.action_export_err, Toast.LENGTH_SHORT).show()
Snackbar.make(appListView, R.string.action_apply_all, Snackbar.LENGTH_LONG).show()
} else Snackbar.make(appListView, R.string.action_export_err, Snackbar.LENGTH_LONG).show()
return true
}
R.id.action_export -> {
clipboard.primaryClip = ClipData.newPlainText(Key.individual,
"${DataStore.bypass}\n${DataStore.individual}")
Toast.makeText(this, R.string.action_export_msg, Toast.LENGTH_SHORT).show()
Snackbar.make(appListView, R.string.action_export_msg, Snackbar.LENGTH_LONG).show()
return true
}
R.id.action_import -> {
@@ -251,23 +254,21 @@ class AppManager : AppCompatActivity(), Toolbar.OnMenuItemClickListener {
bypassSwitch.isChecked = enabled.toBoolean()
DataStore.individual = apps
DataStore.dirty = true
Toast.makeText(this, R.string.action_import_msg, Toast.LENGTH_SHORT).show()
Snackbar.make(appListView, R.string.action_import_msg, Snackbar.LENGTH_LONG).show()
initProxiedApps(apps)
reloadApps()
return true
} catch (_: IllegalArgumentException) { }
}
Toast.makeText(this, R.string.action_import_err, Toast.LENGTH_SHORT).show()
Snackbar.make(appListView, R.string.action_import_err, Snackbar.LENGTH_LONG).show()
}
}
return false
}
override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
return if (keyCode == KeyEvent.KEYCODE_MENU)
if (toolbar.isOverflowMenuShowing) toolbar.hideOverflowMenu() else toolbar.showOverflowMenu()
else super.onKeyUp(keyCode, event)
}
override fun onKeyUp(keyCode: Int, event: KeyEvent?) = if (keyCode == KeyEvent.KEYCODE_MENU)
if (toolbar.isOverflowMenuShowing) toolbar.hideOverflowMenu() else toolbar.showOverflowMenu()
else super.onKeyUp(keyCode, event)
override fun onDestroy() {
instance = null
@@ -26,7 +26,6 @@ import android.os.Bundle
import android.support.design.widget.Snackbar
import android.support.v14.preference.SwitchPreference
import android.support.v7.preference.Preference
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.bg.BaseService
import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.DirectBoot
@@ -21,8 +21,10 @@
package com.github.shadowsocks
import android.app.Activity
import android.app.PendingIntent
import android.app.backup.BackupManager
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.net.VpnService
@@ -56,13 +58,11 @@ import com.github.shadowsocks.utils.Key
import com.github.shadowsocks.utils.responseLength
import com.github.shadowsocks.utils.thread
import com.github.shadowsocks.widget.ServiceButton
import com.mikepenz.crossfader.Crossfader
import com.mikepenz.crossfader.view.CrossFadeSlidingPaneLayout
import com.mikepenz.materialdrawer.Drawer
import com.mikepenz.materialdrawer.DrawerBuilder
import com.mikepenz.materialdrawer.interfaces.ICrossfader
import com.mikepenz.materialdrawer.model.PrimaryDrawerItem
import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem
import java.io.IOException
import java.net.HttpURLConnection
import java.net.InetSocketAddress
import java.net.Proxy
@@ -81,12 +81,14 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, Drawe
private const val DRAWER_FAQ = 4L
private const val DRAWER_CUSTOM_RULES = 5L
fun pendingIntent(context: Context) = PendingIntent.getActivity(context, 0,
Intent(context, MainActivity::class.java).setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT), 0)
var stateListener: ((Int) -> Unit)? = null
}
// UI
private lateinit var fab: ServiceButton
internal var crossfader: Crossfader<CrossFadeSlidingPaneLayout>? = null
internal lateinit var drawer: Drawer
private var previousSelectedDrawer: Long = 0 // it's actually lateinit
@@ -168,9 +170,8 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, Drawe
url.openConnection(Proxy(Proxy.Type.SOCKS,
InetSocketAddress("127.0.0.1", DataStore.portProxy))))
as HttpURLConnection
conn.setRequestProperty("Connection", "close")
conn.instanceFollowRedirects = false
conn.connectTimeout = 10000
conn.readTimeout = 10000
conn.useCaches = false
val (success, result) = try {
val start = SystemClock.elapsedRealtime()
@@ -178,8 +179,8 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, Drawe
val elapsed = SystemClock.elapsedRealtime() - start
if (code == 204 || code == 200 && conn.responseLength == 0L)
Pair(true, getString(R.string.connection_test_available, elapsed))
else throw Exception(getString(R.string.connection_test_error_status_code, code))
} catch (e: Exception) {
else throw IOException(getString(R.string.connection_test_error_status_code, code))
} catch (e: IOException) {
Pair(false, getString(R.string.connection_test_error, e.message))
} finally {
conn.disconnect()
@@ -196,6 +197,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, Drawe
override fun onServiceConnected(service: IShadowsocksService) = changeState(service.state)
override fun onServiceDisconnected() = changeState(BaseService.IDLE)
override fun binderDied() {
super.binderDied()
app.handler.post {
connection.disconnect()
Executable.killAll()
@@ -213,7 +215,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, Drawe
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.layout_main)
val drawerBuilder = DrawerBuilder()
drawer = DrawerBuilder()
.withActivity(this)
.withTranslucentStatusBar(true)
.withHeader(R.layout.layout_header)
@@ -250,28 +252,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, Drawe
.withOnDrawerItemClickListener(this)
.withActionBarDrawerToggle(true)
.withSavedInstance(savedInstanceState)
val miniDrawerWidth = resources.getDimension(R.dimen.material_mini_drawer_item)
if (resources.displayMetrics.widthPixels >=
resources.getDimension(R.dimen.profile_item_max_width) + miniDrawerWidth) {
drawer = drawerBuilder.withGenerateMiniDrawer(true).buildView()
val crossfader = Crossfader<CrossFadeSlidingPaneLayout>()
this.crossfader = crossfader
crossfader
.withContent(findViewById(android.R.id.content))
.withFirst(drawer.slider, resources.getDimensionPixelSize(R.dimen.material_drawer_width))
.withSecond(drawer.miniDrawer.build(this), miniDrawerWidth.toInt())
.withSavedInstance(savedInstanceState)
.build()
if (resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL)
crossfader.crossFadeSlidingPaneLayout.setShadowDrawableRight(
AppCompatResources.getDrawable(this, R.drawable.material_drawer_shadow_right))
else crossfader.crossFadeSlidingPaneLayout.setShadowDrawableLeft(
AppCompatResources.getDrawable(this, R.drawable.material_drawer_shadow_left))
drawer.miniDrawer.withCrossFader(object : ICrossfader { // a wrapper is needed
override fun isCrossfaded(): Boolean = crossfader.isCrossFaded
override fun crossfade() = crossfader.crossFade()
})
} else drawer = drawerBuilder.build()
.build()
if (savedInstanceState == null) displayFragment(ProfilesFragment())
previousSelectedDrawer = drawer.currentSelection
@@ -395,7 +376,6 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, Drawe
override fun onSaveInstanceState(outState: Bundle?) {
super.onSaveInstanceState(outState)
drawer.saveInstanceState(outState)
crossfader?.saveInstanceState(outState)
}
override fun onDestroy() {
@@ -21,6 +21,7 @@
package com.github.shadowsocks
import android.app.Activity
import android.content.BroadcastReceiver
import android.content.Intent
import android.os.Build
import android.os.Bundle
@@ -60,6 +61,7 @@ class ProfileConfigFragment : PreferenceFragmentCompatDividers(), Toolbar.OnMenu
private lateinit var plugin: IconListPreference
private lateinit var pluginConfigure: EditTextPreference
private lateinit var pluginConfiguration: PluginConfiguration
private lateinit var receiver: BroadcastReceiver
override fun onCreatePreferencesFix(savedInstanceState: Bundle?, rootKey: String?) {
preferenceManager.preferenceDataStore = DataStore.privateStore
@@ -80,13 +82,11 @@ class ProfileConfigFragment : PreferenceFragmentCompatDividers(), Toolbar.OnMenu
val serviceMode = DataStore.serviceMode
findPreference(Key.remoteDns).isEnabled = serviceMode != Key.modeProxy
isProxyApps = findPreference(Key.proxyApps) as SwitchPreference
if (Build.VERSION.SDK_INT < 21) isProxyApps.parent!!.removePreference(isProxyApps) else {
isProxyApps.isEnabled = serviceMode == Key.modeVpn
isProxyApps.setOnPreferenceClickListener {
startActivity(Intent(activity, AppManager::class.java))
isProxyApps.isChecked = true
false
}
isProxyApps.isEnabled = serviceMode == Key.modeVpn
isProxyApps.setOnPreferenceClickListener {
startActivity(Intent(activity, AppManager::class.java))
isProxyApps.isChecked = true
false
}
findPreference(Key.udpdns).isEnabled = serviceMode != Key.modeProxy
plugin = findPreference(Key.plugin) as IconListPreference
@@ -104,7 +104,7 @@ class ProfileConfigFragment : PreferenceFragmentCompatDividers(), Toolbar.OnMenu
}
pluginConfigure.onPreferenceChangeListener = this
initPlugins()
app.listenForPackageChanges { initPlugins() }
receiver = app.listenForPackageChanges(false) { initPlugins() }
DataStore.privateStore.registerChangeListener(this)
}
@@ -181,31 +181,30 @@ class ProfileConfigFragment : PreferenceFragmentCompatDividers(), Toolbar.OnMenu
} else super.onActivityResult(requestCode, resultCode, data)
}
override fun onMenuItemClick(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.action_delete -> {
val activity = activity!!
AlertDialog.Builder(activity)
.setTitle(R.string.delete_confirm_prompt)
.setPositiveButton(R.string.yes, { _, _ ->
ProfileManager.delProfile(profile.id)
activity.finish()
})
.setNegativeButton(R.string.no, null)
.create()
.show()
true
}
R.id.action_apply -> {
saveAndExit()
true
}
else -> false
override fun onMenuItemClick(item: MenuItem) = when (item.itemId) {
R.id.action_delete -> {
val activity = activity!!
AlertDialog.Builder(activity)
.setTitle(R.string.delete_confirm_prompt)
.setPositiveButton(R.string.yes, { _, _ ->
ProfileManager.delProfile(profile.id)
activity.finish()
})
.setNegativeButton(R.string.no, null)
.create()
.show()
true
}
R.id.action_apply -> {
saveAndExit()
true
}
else -> false
}
override fun onDestroy() {
DataStore.privateStore.unregisterChangeListener(this)
app.unregisterReceiver(receiver)
super.onDestroy()
}
}
@@ -37,7 +37,6 @@ import android.view.*
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import android.widget.Toast
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.bg.BaseService
import com.github.shadowsocks.bg.TrafficMonitor
@@ -370,12 +369,11 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
val profiles = Profile.findAll(clipboard.primaryClip.getItemAt(0).text).toList()
if (profiles.isNotEmpty()) {
profiles.forEach { ProfileManager.createProfile(it) }
Toast.makeText(activity, R.string.action_import_msg, Toast.LENGTH_SHORT).show()
Snackbar.make(activity!!.findViewById(R.id.snackbar), R.string.action_import_msg,
Snackbar.LENGTH_LONG).show()
return true
}
} catch (exc: Exception) {
app.track(exc)
}
} catch (_: IndexOutOfBoundsException) { }
Snackbar.make(activity!!.findViewById(R.id.snackbar), R.string.action_import_err, Snackbar.LENGTH_LONG)
.show()
true
@@ -386,10 +384,10 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
}
R.id.action_export -> {
val profiles = ProfileManager.getAllProfiles()
if (profiles != null) {
Snackbar.make(activity!!.findViewById(R.id.snackbar), if (profiles != null) {
clipboard.primaryClip = ClipData.newPlainText(null, profiles.joinToString("\n"))
Toast.makeText(activity, R.string.action_export_msg, Toast.LENGTH_SHORT).show()
} else Toast.makeText(activity, R.string.action_export_err, Toast.LENGTH_SHORT).show()
R.string.action_export_msg
} else R.string.action_export_err, Snackbar.LENGTH_LONG).show()
true
}
else -> false
@@ -20,29 +20,40 @@
package com.github.shadowsocks
import android.content.pm.PackageManager
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.pm.ShortcutManager
import android.graphics.BitmapFactory
import android.hardware.camera2.CameraAccessException
import android.hardware.camera2.CameraManager
import android.os.Build
import android.os.Bundle
import android.support.v4.app.ActivityCompat
import android.support.v4.app.TaskStackBuilder
import android.support.v4.content.ContextCompat
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.Toolbar
import android.util.Log
import android.util.SparseArray
import android.view.MenuItem
import android.widget.Toast
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.database.ProfileManager
import com.github.shadowsocks.utils.resolveResourceId
import com.google.zxing.Result
import me.dm7.barcodescanner.zxing.ZXingScannerView
import com.google.android.gms.samples.vision.barcodereader.BarcodeCapture
import com.google.android.gms.samples.vision.barcodereader.BarcodeGraphic
import com.google.android.gms.vision.Frame
import com.google.android.gms.vision.barcode.Barcode
import com.google.android.gms.vision.barcode.BarcodeDetector
import xyz.belvi.mobilevisionbarcodescanner.BarcodeRetriever
class ScannerActivity : AppCompatActivity(), ZXingScannerView.ResultHandler {
class ScannerActivity : AppCompatActivity(), Toolbar.OnMenuItemClickListener, BarcodeRetriever {
companion object {
private const val MY_PERMISSIONS_REQUEST_CAMERA = 1
private const val TAG = "ScannerActivity"
private const val REQUEST_IMPORT = 2
private const val REQUEST_IMPORT_OR_FINISH = 3
}
private lateinit var scannerView: ZXingScannerView
private fun navigateUp() {
val intent = parentActivityIntent
if (shouldUpRecreateTask(intent) || isTaskRoot)
@@ -52,45 +63,79 @@ class ScannerActivity : AppCompatActivity(), ZXingScannerView.ResultHandler {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (Build.VERSION.SDK_INT >= 25) getSystemService(ShortcutManager::class.java).reportShortcutUsed("scan")
if (try {
(getSystemService(Context.CAMERA_SERVICE) as CameraManager).cameraIdList.isEmpty()
} catch (_: CameraAccessException) {
true
}) {
startImport()
return
}
setContentView(R.layout.layout_scanner)
val toolbar = findViewById<Toolbar>(R.id.toolbar)
toolbar.title = title
toolbar.setNavigationIcon(theme.resolveResourceId(R.attr.homeAsUpIndicator))
toolbar.setNavigationOnClickListener { navigateUp() }
scannerView = findViewById(R.id.scanner)
if (Build.VERSION.SDK_INT >= 25) getSystemService(ShortcutManager::class.java).reportShortcutUsed("scan")
toolbar.inflateMenu(R.menu.scanner_menu)
toolbar.setOnMenuItemClickListener(this)
(supportFragmentManager.findFragmentById(R.id.barcode) as BarcodeCapture).setRetrieval(this)
}
override fun onResume() {
super.onResume()
val permissionCheck = ContextCompat.checkSelfPermission(this,
android.Manifest.permission.CAMERA)
if (permissionCheck == PackageManager.PERMISSION_GRANTED) {
scannerView.setResultHandler(this) // Register ourselves as a handler for scan results.
scannerView.startCamera() // Start camera on resume
} else ActivityCompat.requestPermissions(this, arrayOf(android.Manifest.permission.CAMERA),
MY_PERMISSIONS_REQUEST_CAMERA)
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
if (requestCode == MY_PERMISSIONS_REQUEST_CAMERA)
if (grantResults.getOrNull(0) == PackageManager.PERMISSION_GRANTED) {
scannerView.setResultHandler(this)
scannerView.startCamera()
} else {
Toast.makeText(this, R.string.add_profile_scanner_permission_required, Toast.LENGTH_SHORT).show()
finish()
}
else super.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
override fun onPause() {
super.onPause()
scannerView.stopCamera() // Stop camera on pause
}
override fun handleResult(rawResult: Result?) {
Profile.findAll(rawResult?.text).forEach { ProfileManager.createProfile(it) }
override fun onRetrieved(barcode: Barcode) = runOnUiThread {
Profile.findAll(barcode.rawValue).forEach { ProfileManager.createProfile(it) }
navigateUp()
}
override fun onRetrievedMultiple(closetToClick: Barcode?, barcode: MutableList<BarcodeGraphic>?) = check(false)
override fun onBitmapScanned(sparseArray: SparseArray<Barcode>?) { }
override fun onRetrievedFailed(reason: String?) {
Log.w(TAG, reason)
}
override fun onPermissionRequestDenied() {
Toast.makeText(this, R.string.add_profile_scanner_permission_required, Toast.LENGTH_SHORT).show()
startImport()
}
override fun onMenuItemClick(item: MenuItem) = when (item.itemId) {
R.id.action_import -> {
startImport(true)
true
}
else -> false
}
private fun startImport(manual: Boolean = false) = startActivityForResult(Intent(Intent.ACTION_OPEN_DOCUMENT)
.addCategory(Intent.CATEGORY_OPENABLE)
.setType("image/*")
.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true), if (manual) REQUEST_IMPORT else REQUEST_IMPORT_OR_FINISH)
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
REQUEST_IMPORT, REQUEST_IMPORT_OR_FINISH -> if (resultCode == Activity.RESULT_OK) {
var success = false
val detector = BarcodeDetector.Builder(this)
.setBarcodeFormats(Barcode.QR_CODE)
.build()
if (detector.isOperational) {
var list = listOfNotNull(data?.data)
val clipData = data?.clipData
if (clipData != null) list += (0 until clipData.itemCount).map { clipData.getItemAt(it).uri }
val resolver = contentResolver
for (uri in list) try {
val barcodes = detector.detect(Frame.Builder()
.setBitmap(BitmapFactory.decodeStream(resolver.openInputStream(uri))).build())
for (i in 0 until barcodes.size()) Profile.findAll(barcodes.valueAt(i).rawValue).forEach {
ProfileManager.createProfile(it)
success = true
}
} catch (e: Exception) {
app.track(e)
}
} else Log.w(TAG, "Google vision isn't operational.")
Toast.makeText(this, if (success) R.string.action_import_msg else R.string.action_import_err,
Toast.LENGTH_SHORT).show()
navigateUp()
} else if (requestCode == REQUEST_IMPORT_OR_FINISH) navigateUp()
else -> super.onActivityResult(requestCode, resultCode, data)
}
}
}
@@ -48,7 +48,9 @@ class ShadowsocksConnection(private val instance: Interface) : ServiceConnection
* Different from Android framework, this method will be called even when you call `detachService`.
*/
fun onServiceDisconnected() { }
override fun binderDied() { }
override fun binderDied() {
connection.service = null
}
}
private var connectionActive = false
@@ -109,6 +111,8 @@ class ShadowsocksConnection(private val instance: Interface) : ServiceConnection
connectionActive = false
if (instance.listenForDeath) binder?.unlinkToDeath(instance, 0)
binder = null
if (instance.serviceCallback != null)
service?.stopListeningForBandwidth(instance.serviceCallback)
service = null
}
}
@@ -35,7 +35,7 @@ open class ToolbarFragment : Fragment() {
super.onViewCreated(view, savedInstanceState)
toolbar = view.findViewById(R.id.toolbar)
val activity = activity as MainActivity
if (activity.crossfader == null) activity.drawer.setToolbar(activity, toolbar, true)
activity.drawer.setToolbar(activity, toolbar, true)
}
open fun onTrafficUpdated(profileId: Int, txRate: Long, rxRate: Long, txTotal: Long, rxTotal: Long) { }
@@ -20,7 +20,6 @@
package com.github.shadowsocks
import android.app.Activity
import android.app.KeyguardManager
import android.content.BroadcastReceiver
import android.content.Context
@@ -28,13 +27,14 @@ 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 com.github.shadowsocks.aidl.IShadowsocksService
import com.github.shadowsocks.bg.BaseService
import com.github.shadowsocks.utils.broadcastReceiver
class VpnRequestActivity : Activity(), ShadowsocksConnection.Interface {
class VpnRequestActivity : AppCompatActivity(), ShadowsocksConnection.Interface {
companion object {
private const val TAG = "VpnRequestActivity"
private const val REQUEST_CONNECT = 1
@@ -56,11 +56,9 @@ class VpnRequestActivity : Activity(), ShadowsocksConnection.Interface {
}
override fun onServiceConnected(service: IShadowsocksService) {
app.handler.postDelayed({
val intent = VpnService.prepare(this)
if (intent == null) onActivityResult(REQUEST_CONNECT, RESULT_OK, null)
else startActivityForResult(intent, REQUEST_CONNECT)
}, 1000)
val intent = VpnService.prepare(this)
if (intent == null) onActivityResult(REQUEST_CONNECT, RESULT_OK, null)
else startActivityForResult(intent, REQUEST_CONNECT)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
@@ -23,22 +23,21 @@ package com.github.shadowsocks.acl
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.os.Build
import android.os.Bundle
import android.support.design.widget.Snackbar
import android.support.design.widget.TextInputLayout
import android.support.v4.content.ContextCompat
import android.support.v7.app.AlertDialog
import android.support.v7.widget.DefaultItemAnimator
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.support.v7.widget.Toolbar
import android.support.v7.widget.helper.ItemTouchHelper
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.EditText
import android.widget.Spinner
import android.widget.TextView
import android.widget.Toast
import android.text.Editable
import android.text.TextWatcher
import android.view.*
import android.widget.*
import com.futuremind.recyclerviewfastscroll.FastScroller
import com.futuremind.recyclerviewfastscroll.SectionTitleProvider
import com.github.shadowsocks.App.Companion.app
@@ -48,12 +47,14 @@ import com.github.shadowsocks.ToolbarFragment
import com.github.shadowsocks.bg.BaseService
import com.github.shadowsocks.utils.Subnet
import com.github.shadowsocks.utils.asIterable
import com.github.shadowsocks.utils.resolveResourceId
import com.github.shadowsocks.widget.UndoSnackbarManager
import java.net.IDN
import java.net.MalformedURLException
import java.net.URL
import java.util.*
class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener, ActionMode.Callback {
companion object {
private const val TEMPLATE_REGEX_DOMAIN = "(^|\\.)%s$"
@@ -70,6 +71,92 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
Domain,
Url;
}
private inner class AclRuleDialog(item: Any = "") : TextWatcher, AdapterView.OnItemSelectedListener {
val builder: AlertDialog.Builder
val templateSelector: Spinner
val editText: EditText
private val inputLayout: TextInputLayout
private lateinit var dialog: AlertDialog
private lateinit var positive: Button
init {
val activity = activity!!
val view = activity.layoutInflater.inflate(R.layout.dialog_acl_rule, null)
templateSelector = view.findViewById(R.id.template_selector)
editText = view.findViewById(R.id.content)
inputLayout = view.findViewById(R.id.content_layout)
when (item) {
is String -> {
val match = PATTERN_DOMAIN.find(item)
if (match != null) {
templateSelector.setSelection(Template.Domain.ordinal)
editText.setText(IDN.toUnicode(match.value.replace("\\.", "."),
IDN.ALLOW_UNASSIGNED or IDN.USE_STD3_ASCII_RULES))
} else {
templateSelector.setSelection(Template.Generic.ordinal)
editText.setText(item)
}
}
is URL -> {
templateSelector.setSelection(Template.Url.ordinal)
editText.setText(item.toString())
}
else -> {
templateSelector.setSelection(Template.Generic.ordinal)
editText.setText(item.toString())
}
}
templateSelector.onItemSelectedListener = this
editText.addTextChangedListener(this)
builder = AlertDialog.Builder(activity)
.setTitle(R.string.edit_rule)
.setNegativeButton(android.R.string.cancel, null)
.setView(view)
}
fun show() {
dialog = builder.create()
dialog.show()
positive = dialog.getButton(AlertDialog.BUTTON_POSITIVE)
validate()
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { }
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { }
override fun afterTextChanged(s: Editable) = validate(value = s)
override fun onNothingSelected(parent: AdapterView<*>?) = check(false)
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) = validate(position)
private fun validate(template: Int = templateSelector.selectedItemPosition, value: Editable = editText.text) {
val error = when (Template.values()[template]) {
Template.Generic -> if (value.isEmpty()) "" else null
Template.Domain -> try {
IDN.toASCII(value.toString(), IDN.ALLOW_UNASSIGNED or IDN.USE_STD3_ASCII_RULES)
null
} catch (e: IllegalArgumentException) {
e.cause?.message ?: e.message
}
Template.Url -> try {
URL(value.toString())
null
} catch (e: MalformedURLException) {
e.message
}
}
inputLayout.error = error
positive.isEnabled = error == null
}
fun add(): Int? {
val text = editText.text.toString()
return when (Template.values()[templateSelector.selectedItemPosition]) {
Template.Generic -> adapter.addToProxy(text)
Template.Domain -> adapter.addHostname(TEMPLATE_REGEX_DOMAIN.format(Locale.ENGLISH, IDN.toASCII(text,
IDN.ALLOW_UNASSIGNED or IDN.USE_STD3_ASCII_RULES).replace(".", "\\.")))
Template.Url -> adapter.addURL(URL(text))
}
}
}
private inner class AclRuleViewHolder(view: View) : RecyclerView.ViewHolder(view),
View.OnClickListener, View.OnLongClickListener {
@@ -103,19 +190,18 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
override fun onClick(v: View?) {
if (selectedItems.isNotEmpty()) onLongClick(v) else {
val (templateSelector, editText, dialog) = createAclRuleDialog(item)
dialog
val dialog = AclRuleDialog(item)
dialog.builder
.setNeutralButton(R.string.delete, { _, _ ->
adapter.remove(item)
undoManager.remove(Pair(-1, item))
})
.setPositiveButton(android.R.string.ok, { _, _ ->
adapter.remove(item)
val index = adapter.addFromTemplate(templateSelector.selectedItemPosition,
editText.text.toString()) ?: adapter.add(item)
val index = dialog.add() ?: adapter.add(item)
if (index != null) list.post { list.scrollToPosition(index) }
})
.create().show()
dialog.show()
}
}
override fun onLongClick(p0: View?): Boolean {
@@ -142,16 +228,14 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
override fun getItemCount(): Int = acl.subnets.size() + acl.hostnames.size() + acl.urls.size()
override fun getSectionTitle(i: Int): String {
val j = i - acl.subnets.size()
return try {
(if (j < 0) acl.subnets[i].address.hostAddress.substring(0, 1) else {
val k = j - acl.hostnames.size()
if (k < 0) {
val hostname = acl.hostnames[j]
// don't convert IDN yet
PATTERN_DOMAIN.find(hostname)?.value?.replace("\\.", ".") ?: hostname
} else acl.urls[k].host
}).substring(0, 1)
} catch (_: IndexOutOfBoundsException) { " " }
return (if (j < 0) acl.subnets[i].address.hostAddress.substring(0, 1) else {
val k = j - acl.hostnames.size()
if (k < 0) {
val hostname = acl.hostnames[j]
// don't convert IDN yet
PATTERN_DOMAIN.find(hostname)?.value?.replace("\\.", ".") ?: hostname
} else acl.urls[k].host
}).firstOrNull()?.toString() ?: " "
}
private fun apply() {
@@ -207,20 +291,6 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
.forEach { if (result == null) result = it }
return result
}
fun addFromTemplate(templateValue: Int, text: String): Int? {
val template = Template.values()[templateValue]
return when (template) {
Template.Generic -> addToProxy(text)
Template.Domain -> try {
addHostname(TEMPLATE_REGEX_DOMAIN.format(Locale.ENGLISH, IDN.toASCII(text,
IDN.ALLOW_UNASSIGNED or IDN.USE_STD3_ASCII_RULES).replace(".", "\\.")))
} catch (exc: IllegalArgumentException) {
Toast.makeText(activity, exc.message, Toast.LENGTH_SHORT).show()
null
}
Template.Url -> addURL(URL(text))
}
}
fun remove(i: Int) {
val j = i - acl.subnets.size()
@@ -288,44 +358,12 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
private val selectedItems = HashSet<Any>()
private val adapter by lazy { AclRulesAdapter() }
private lateinit var list: RecyclerView
private var selectionItem: MenuItem? = null
private var mode: ActionMode? = null
private lateinit var undoManager: UndoSnackbarManager<Any>
private val clipboard by lazy { activity!!.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager }
private fun onSelectedItemsUpdated() {
val selectionItem = selectionItem
if (selectionItem != null) selectionItem.isVisible = selectedItems.isNotEmpty()
}
private fun createAclRuleDialog(item: Any = ""): Triple<Spinner, EditText, AlertDialog.Builder> {
val view = activity!!.layoutInflater.inflate(R.layout.dialog_acl_rule, null)
val templateSelector = view.findViewById<Spinner>(R.id.template_selector)
val editText = view.findViewById<EditText>(R.id.content)
when (item) {
is String -> {
val match = PATTERN_DOMAIN.find(item)
if (match != null) {
templateSelector.setSelection(Template.Domain.ordinal)
editText.setText(IDN.toUnicode(match.value.replace("\\.", "."),
IDN.ALLOW_UNASSIGNED or IDN.USE_STD3_ASCII_RULES))
} else {
templateSelector.setSelection(Template.Generic.ordinal)
editText.setText(item)
}
}
is URL -> {
templateSelector.setSelection(Template.Url.ordinal)
editText.setText(item.toString())
}
else -> {
templateSelector.setSelection(Template.Generic.ordinal)
editText.setText(item.toString())
}
}
return Triple(templateSelector, editText, AlertDialog.Builder(activity!!)
.setTitle(R.string.edit_rule)
.setNegativeButton(android.R.string.cancel, null)
.setView(view))
if (selectedItems.isEmpty()) mode?.finish() else if (mode == null) mode = toolbar.startActionMode(this)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
@@ -345,9 +383,6 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
toolbar.setTitle(R.string.custom_rules)
toolbar.inflateMenu(R.menu.custom_rules_menu)
toolbar.setOnMenuItemClickListener(this)
val selectionItem = toolbar.menu.findItem(R.id.selection)
selectionItem.isVisible = selectedItems.isNotEmpty()
this.selectionItem = selectionItem
list = view.findViewById(R.id.list)
list.layoutManager = LinearLayoutManager(activity, LinearLayoutManager.VERTICAL, false)
list.itemAnimator = DefaultItemAnimator()
@@ -365,10 +400,9 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
}
override fun onBackPressed(): Boolean {
return if (selectedItems.isNotEmpty()) {
selectedItems.clear()
onSelectedItemsUpdated()
adapter.notifyDataSetChanged()
val mode = mode
return if (mode != null) {
mode.finish()
true
} else super.onBackPressed()
}
@@ -395,29 +429,10 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
}
override fun onMenuItemClick(item: MenuItem): Boolean = when (item.itemId) {
R.id.action_select_all -> {
adapter.selectAll()
true
}
R.id.action_cut -> {
copySelected()
adapter.removeSelected()
true
}
R.id.action_copy -> {
copySelected()
true
}
R.id.action_delete -> {
adapter.removeSelected()
true
}
R.id.action_manual_settings -> {
val (templateSelector, editText, dialog) = createAclRuleDialog()
dialog.setPositiveButton(android.R.string.ok, { _, _ ->
adapter.addFromTemplate(templateSelector.selectedItemPosition, editText.text.toString())
}).create().show()
val dialog = AclRuleDialog()
dialog.builder.setPositiveButton(android.R.string.ok, { _, _ -> dialog.add() })
dialog.show()
true
}
R.id.action_import -> {
@@ -442,6 +457,53 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
override fun onDetach() {
undoManager.flush()
mode?.finish()
super.onDetach()
}
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
val activity = activity!!
val window = activity.window
// In the end material_grey_100 is used for background, see AppCompatDrawableManager (very complicated)
if (Build.VERSION.SDK_INT >= 23) {
window.statusBarColor = ContextCompat.getColor(activity, R.color.material_grey_300)
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
} else window.statusBarColor = ContextCompat.getColor(activity, R.color.material_grey_600)
activity.menuInflater.inflate(R.menu.custom_rules_selection, menu)
toolbar.touchscreenBlocksFocus = true
return true
}
override fun onPrepareActionMode(mode: ActionMode, menu: Menu): Boolean = false
override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean = when (item.itemId) {
R.id.action_select_all -> {
adapter.selectAll()
true
}
R.id.action_cut -> {
copySelected()
adapter.removeSelected()
true
}
R.id.action_copy -> {
copySelected()
true
}
R.id.action_delete -> {
adapter.removeSelected()
true
}
else -> false
}
override fun onDestroyActionMode(mode: ActionMode) {
val activity = activity!!
val window = activity.window
window.statusBarColor = ContextCompat.getColor(activity,
activity.theme.resolveResourceId(android.R.attr.statusBarColor))
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE
toolbar.touchscreenBlocksFocus = false
selectedItems.clear()
onSelectedItemsUpdated()
adapter.notifyDataSetChanged()
this.mode = null
}
}
@@ -31,7 +31,6 @@ import android.os.RemoteCallbackList
import android.support.v4.os.UserManagerCompat
import android.util.Base64
import android.util.Log
import android.widget.Toast
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.R
import com.github.shadowsocks.acl.Acl
@@ -88,17 +87,14 @@ object BaseService {
val closeReceiver = broadcastReceiver { _, intent ->
when (intent.action) {
Action.RELOAD -> service.forceLoad()
else -> {
Toast.makeText(service as Context, R.string.stopping, Toast.LENGTH_SHORT).show()
service.stopRunner(true)
}
else -> service.stopRunner(true)
}
}
var closeReceiverRegistered = false
val binder = object : IShadowsocksService.Stub() {
override fun getState(): Int = this@Data.state
override fun getProfileName(): String = profile?.formattedName ?: "Idle"
override fun getProfileName(): String = profile?.name ?: "Idle"
override fun registerCallback(cb: IShadowsocksServiceCallback) {
callbacks.register(cb)
@@ -149,18 +145,18 @@ object BaseService {
}
internal fun updateTrafficTotal(tx: Long, rx: Long) {
val profile = profile ?: return
val p = ProfileManager.getProfile(profile.id) ?: return // profile may have host, etc. modified
p.tx += tx
p.rx += rx
ProfileManager.updateProfile(p)
// this.profile may have host, etc. modified and thus a re-fetch is necessary (possible race condition)
val profile = ProfileManager.getProfile((profile ?: return).id) ?: return
profile.tx += tx
profile.rx += rx
ProfileManager.updateProfile(profile)
app.handler.post {
if (bandwidthListeners.isNotEmpty()) {
val n = callbacks.beginBroadcast()
for (i in 0 until n) {
try {
val item = callbacks.getBroadcastItem(i)
if (bandwidthListeners.contains(item.asBinder())) item.trafficPersisted(p.id)
if (bandwidthListeners.contains(item.asBinder())) item.trafficPersisted(profile.id)
} catch (_: Exception) { } // ignore
}
callbacks.finishBroadcast()
@@ -185,9 +181,9 @@ object BaseService {
.put("plugin_opts", plugin.toString())
}
// sensitive Shadowsocks config is stored in
val file = File((if (UserManagerCompat.isUserUnlocked(app)) app.filesDir else @TargetApi(24) {
val file = File(if (UserManagerCompat.isUserUnlocked(app)) app.filesDir else @TargetApi(24) {
app.deviceContext.noBackupFilesDir // only API 24+ will be in locked state
}), CONFIG_FILE)
}, CONFIG_FILE)
shadowsocksConfigFile = file
file.writeText(config.toString())
return file
@@ -221,9 +217,9 @@ object BaseService {
false
} else true
fun forceLoad() {
val p = app.currentProfile
val profile = app.currentProfile
?: return stopRunner(true, (this as Context).getString(R.string.profile_empty))
if (!checkProfile(p)) return
if (!checkProfile(profile)) return
val s = data.state
when (s) {
STOPPED -> startRunner()
@@ -322,6 +318,7 @@ object BaseService {
stopRunner(true, getString(R.string.profile_empty))
return Service.START_NOT_STICKY
}
profile.name = profile.formattedName // save name for later queries
data.profile = profile
TrafficMonitor.reset()
@@ -20,56 +20,23 @@
package com.github.shadowsocks.bg
import android.content.Context
import android.net.ConnectivityManager
import android.net.LinkProperties
import android.os.Build
import android.util.Log
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.BuildConfig
import okhttp3.Dns as Okdns
import org.xbill.DNS.*
import java.net.Inet6Address
import java.net.InetAddress
import java.net.NetworkInterface
import java.net.UnknownHostException
import java.util.*
import java.io.IOException
import java.net.*
object Dns {
private const val TAG = "Dns"
@Throws(Exception::class)
fun getDnsResolver(context: Context): String {
val dnsResolvers = getDnsResolvers(context)
if (dnsResolvers.isEmpty()) throw Exception("Couldn't find an active DNS resolver")
val dnsResolver = dnsResolvers.iterator().next().toString()
if (dnsResolver.startsWith("/")) return dnsResolver.substring(1)
return dnsResolver
}
@Throws(Exception::class)
private fun getDnsResolvers(context: Context): Collection<InetAddress> {
val addresses = ArrayList<InetAddress>()
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val classLinkProperties = Class.forName("android.net.LinkProperties")
val getActiveLinkPropertiesMethod = ConnectivityManager::class.java.getMethod("getActiveLinkProperties")
val linkProperties = getActiveLinkPropertiesMethod.invoke(connectivityManager)
if (linkProperties != null) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
val dnses = classLinkProperties.getMethod("getDnses").invoke(linkProperties) as Collection<*>
dnses.mapTo(addresses) { it as InetAddress }
} else addresses.addAll((linkProperties as LinkProperties).dnsServers)
}
return addresses
}
private val hasIPv6Support get() = try {
val result = NetworkInterface.getNetworkInterfaces().asSequence().flatMap { it.inetAddresses.asSequence() }
.count { it is Inet6Address && !it.isLoopbackAddress && !it.isLinkLocalAddress } > 0
.any { it is Inet6Address && !it.isLoopbackAddress && !it.isLinkLocalAddress }
if (result && BuildConfig.DEBUG) Log.d(TAG, "IPv6 address detected")
result
} catch (ex: Exception) {
Log.e(TAG, "Failed to get interfaces' addresses.")
} catch (ex: SocketException) {
Log.e(TAG, "Failed to get interfaces' addresses.", ex)
app.track(ex)
false
}
@@ -90,7 +57,7 @@ object Dns {
Type.AAAA -> return (r as AAAARecord).address.hostAddress
}
}
} catch (_: Exception) { }
} catch (_: IOException) { }
return null
}
private fun resolve(host: String): String? = try {
@@ -25,6 +25,7 @@ import android.util.Log
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.JniHelper
import java.io.File
import java.io.FileNotFoundException
object Executable {
const val REDSOCKS = "libredsocks.so"
@@ -37,7 +38,11 @@ object Executable {
fun killAll() {
for (process in File("/proc").listFiles { _, name -> TextUtils.isDigitsOnly(name) }) {
val exe = File(File(process, "cmdline").readText().split(Character.MIN_VALUE, limit = 2).first())
val exe = File(try {
File(process, "cmdline").readText()
} 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")
@@ -20,7 +20,6 @@
package com.github.shadowsocks.bg
import android.content.Context
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.acl.Acl
import com.github.shadowsocks.preference.DataStore
@@ -47,14 +46,14 @@ object LocalDnsService {
val data = data
val profile = data.profile!!
fun makeDns(name: String, address: String, edns: Boolean = true): JSONObject {
fun makeDns(name: String, address: String, timeout: Int, edns: Boolean = true): JSONObject {
val dns = JSONObject()
.put("Name", name)
.put("Address", (when (address.parseNumericAddress()) {
.put("Address", when (address.parseNumericAddress()) {
is Inet6Address -> "[$address]"
else -> address
}) + ":53")
.put("Timeout", 6)
})
.put("Timeout", timeout)
.put("EDNSClientSubnet", JSONObject().put("Policy", "disable"))
if (edns) dns
.put("Protocol", "tcp")
@@ -70,21 +69,17 @@ object LocalDnsService {
.put("RedirectIPv6Record", true)
.put("DomainBase64Decode", false)
.put("HostsFile", "hosts")
.put("MinimumTTL", 3600)
.put("MinimumTTL", 120)
.put("CacheSize", 4096)
val remoteDns = JSONArray(profile.remoteDns.split(",")
.mapIndexed { i, dns -> makeDns("UserDef-" + i, dns.trim()) })
.mapIndexed { i, dns -> makeDns("UserDef-" + i,
dns.trim() + ":53", 9) })
val localDns = JSONArray(arrayOf(
makeDns("Primary-1", "119.29.29.29", false),
makeDns("Primary-2", "114.114.114.114", false)
makeDns("Primary-1", "119.29.29.29:53", 3, false),
makeDns("Primary-2", "114.114.114.114:53", 3, false),
makeDns("Primary-3", "208.67.222.222:443", 3, false)
))
this as Context
try {
val localLinkDns = Dns.getDnsResolver(this)
localDns.put(makeDns("Primary-3", localLinkDns, false))
} catch (_: Exception) { } // ignore
when (profile.route) {
Acl.BYPASS_CHN, Acl.BYPASS_LAN_CHN, Acl.GFWLIST, Acl.CUSTOM_RULES -> config
.put("PrimaryDNS", localDns)
@@ -41,7 +41,7 @@ abstract class LocalSocketListener(protected val tag: String) : Thread() {
* Inherited class do not need to close input/output streams as they will be closed automatically.
*/
protected abstract fun accept(socket: LocalSocket)
override final fun run() {
final override fun run() {
socketFile.delete() // It's a must-have to close and reuse previous local socket.
LocalSocket().use { localSocket ->
val serverSocket = try {
@@ -23,7 +23,6 @@ package com.github.shadowsocks.bg
import android.app.Service
import android.content.Intent
import android.os.IBinder
import com.github.shadowsocks.database.Profile
/**
* Shadowsocks service at its minimum.
@@ -40,8 +40,7 @@ import java.util.*
/**
* Android < 8 VPN: always invisible because of VPN notification/icon
* Android 4.x other: always visible
* Android 5-7 other: only invisible in (possibly unsecure) lockscreen
* Android < 8 other: only invisible in (possibly unsecure) lockscreen
* Android 8+: always visible due to system limitations
* (user can choose to hide the notification in secure lockscreen or anywhere)
*/
@@ -74,8 +73,7 @@ class ServiceNotification(private val service: BaseService.Interface, profileNam
.setColor(ContextCompat.getColor(service, R.color.material_primary_500))
.setTicker(service.getString(R.string.forward_success))
.setContentTitle(profileName)
.setContentIntent(PendingIntent.getActivity(service, 0, Intent(service, MainActivity::class.java)
.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT), 0))
.setContentIntent(MainActivity.pendingIntent(service))
.setSmallIcon(R.drawable.ic_service_active)
private val style = NotificationCompat.BigTextStyle(builder)
private var isVisible = true
@@ -85,24 +83,22 @@ class ServiceNotification(private val service: BaseService.Interface, profileNam
if (Build.VERSION.SDK_INT < 24) builder.addAction(R.drawable.ic_navigation_close,
service.getString(R.string.stop), PendingIntent.getBroadcast(service, 0, Intent(Action.CLOSE), 0))
val power = service.getSystemService(Context.POWER_SERVICE) as PowerManager
update(if (if (Build.VERSION.SDK_INT >= 20) power.isInteractive else @Suppress("DEPRECATION") power.isScreenOn)
Intent.ACTION_SCREEN_ON else Intent.ACTION_SCREEN_OFF, true)
update(if (power.isInteractive) Intent.ACTION_SCREEN_ON else Intent.ACTION_SCREEN_OFF, true)
val screenFilter = IntentFilter()
screenFilter.addAction(Intent.ACTION_SCREEN_ON)
screenFilter.addAction(Intent.ACTION_SCREEN_OFF)
if (visible && Build.VERSION.SDK_INT in 21 until 26) screenFilter.addAction(Intent.ACTION_USER_PRESENT)
if (visible && Build.VERSION.SDK_INT < 26) screenFilter.addAction(Intent.ACTION_USER_PRESENT)
service.registerReceiver(lockReceiver, screenFilter)
}
private fun update(action: String, forceShow: Boolean = false) {
if (forceShow || service.data.state == BaseService.CONNECTED) when (action) {
Intent.ACTION_SCREEN_OFF -> {
setVisible(visible && Build.VERSION.SDK_INT < 21, forceShow)
setVisible(false, forceShow)
unregisterCallback() // unregister callback to save battery
}
Intent.ACTION_SCREEN_ON -> {
setVisible(visible && (Build.VERSION.SDK_INT < 21 || !keyGuard.inKeyguardRestrictedInputMode()),
forceShow)
setVisible(visible && !keyGuard.inKeyguardRestrictedInputMode(), forceShow)
service.data.binder.registerCallback(callback)
service.data.binder.startListeningForBandwidth(callback)
callbackRegistered = true
@@ -30,11 +30,12 @@ import com.github.shadowsocks.R
import com.github.shadowsocks.ShadowsocksConnection
import com.github.shadowsocks.aidl.IShadowsocksService
import com.github.shadowsocks.aidl.IShadowsocksServiceCallback
import com.github.shadowsocks.preference.DataStore
import android.service.quicksettings.TileService as BaseTileService
@RequiresApi(24)
class TileService : BaseTileService(), ShadowsocksConnection.Interface {
private val iconIdle by lazy { Icon.createWithResource(this, R.drawable.ic_service_idle).setTint(0x79ffffff) }
private val iconIdle by lazy { Icon.createWithResource(this, R.drawable.ic_service_idle) }
private val iconBusy by lazy { Icon.createWithResource(this, R.drawable.ic_service_busy) }
private val iconConnected by lazy { Icon.createWithResource(this, R.drawable.ic_service_active) }
private val keyguard by lazy { getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager }
@@ -81,7 +82,7 @@ class TileService : BaseTileService(), ShadowsocksConnection.Interface {
}
override fun onClick() {
if (isLocked) unlockAndRun(this::toggle) else toggle()
if (isLocked && !DataStore.directBootAware) unlockAndRun(this::toggle) else toggle()
}
private fun toggle() {
@@ -44,7 +44,7 @@ object TrafficMonitor {
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: Double = size.toDouble()
var n = size.toDouble()
var i = -1
while (n >= 999.5) {
n /= 1024
@@ -37,8 +37,7 @@ class TrafficMonitorThread : LocalSocketListener("TrafficMonitorThread") {
if (socket.inputStream.read(buffer) != 16) throw IOException("Unexpected traffic stat length")
val stat = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN)
TrafficMonitor.update(stat.getLong(0), stat.getLong(8))
socket.outputStream.write(0)
} catch (e: Exception) {
} catch (e: IOException) {
Log.e(tag, "Error when recv traffic stat", e)
app.track(e)
}
@@ -24,12 +24,12 @@ import android.app.Service
import android.content.Intent
import android.content.pm.PackageManager
import android.net.LocalSocket
import android.os.Build
import android.os.IBinder
import android.os.ParcelFileDescriptor
import android.util.Log
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.JniHelper
import com.github.shadowsocks.MainActivity
import com.github.shadowsocks.R
import com.github.shadowsocks.VpnRequestActivity
import com.github.shadowsocks.acl.Acl
@@ -38,6 +38,7 @@ import com.github.shadowsocks.utils.Subnet
import com.github.shadowsocks.utils.parseNumericAddress
import java.io.File
import java.io.FileDescriptor
import java.io.IOException
import java.lang.reflect.Method
import java.util.*
import android.net.VpnService as BaseVpnService
@@ -55,18 +56,24 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
override val socketFile: File = File(app.deviceContext.filesDir, "protect_path")
override fun accept(socket: LocalSocket) {
var success = false
try {
socket.inputStream.read()
val fds = socket.ancillaryFileDescriptors
if (fds.isEmpty()) return
val fd = getInt.invoke(fds.first()) as Int
val ret = protect(fd)
success = protect(fd)
JniHelper.close(fd) // Trick to close file decriptor
socket.outputStream.write(if (ret) 0 else 1)
} catch (e: Exception) {
Log.e(tag, "Error when protect socket", e)
app.track(e)
}
try {
socket.outputStream.write(if (success) 0 else 1)
} catch (e: IOException) {
Log.e(tag, "Error when returning result in protect", e)
app.track(e)
}
}
}
class NullConnectionException : NullPointerException()
@@ -118,7 +125,7 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
super.startNativeProcesses()
val fd = startVpn()
if (!sendFd(fd)) throw Exception("sendFd failed")
if (!sendFd(fd)) throw IOException("sendFd failed")
}
override fun buildAdditionalArguments(cmd: ArrayList<String>): ArrayList<String> {
@@ -129,6 +136,7 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
private fun startVpn(): Int {
val profile = data.profile!!
val builder = Builder()
.setConfigureIntent(MainActivity.pendingIntent(this))
.setSession(profile.formattedName)
.setMtu(VPN_MTU)
.addAddress(PRIVATE_VLAN.format(Locale.ENGLISH, "1"), 24)
@@ -140,7 +148,7 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
builder.addRoute("::", 0)
}
if (Build.VERSION.SDK_INT >= 21 && profile.proxyApps) {
if (profile.proxyApps) {
val me = packageName
profile.individual.split('\n')
.filter { it != me }
@@ -72,10 +72,10 @@ object PrivateDatabase : OrmLiteSqliteOpenHelper(app, Key.DB_PROFILE, null, 25)
profileDao.executeRawNoArgs("ALTER TABLE `profile` RENAME TO `tmp`;")
TableUtils.createTable(connectionSource, Profile::class.java)
profileDao.executeRawNoArgs(
"INSERT INTO `profile`(id, name, host, localPort, remotePort, password, method, route, proxyApps, bypass," +
" udpdns, ipv6, individual) " +
"SELECT id, name, host, localPort, remotePort, password, method, route, 1 - global, bypass, udpdns, ipv6," +
" individual FROM `tmp`;")
"INSERT INTO `profile`(id, name, host, localPort, remotePort, password, method, route," +
" proxyApps, bypass, udpdns, ipv6, individual) " +
"SELECT id, name, host, localPort, remotePort, password, method, route, 1 - global," +
" bypass, udpdns, ipv6, individual FROM `tmp`;")
profileDao.executeRawNoArgs("DROP TABLE `tmp`;")
profileDao.executeRawNoArgs("COMMIT;")
} else if (oldVersion < 13) {
@@ -36,6 +36,7 @@ import java.util.*
class Profile : Serializable {
companion object {
private const val TAG = "ShadowParser"
private const val serialVersionUID = 0L
private val pattern = """(?i)ss://[-a-zA-Z0-9+&@#/%?=~_|!:,.;\[\]]*[-a-zA-Z0-9+&@#/%=~_|\[\]]""".toRegex()
private val userInfoPattern = "^(.+?):(.*)$".toRegex()
private val legacyPattern = "^(.+?):(.*)@(.+?):(\\d+?)$".toRegex()
@@ -25,79 +25,69 @@ 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 java.sql.SQLException
/**
* SQLExceptions are not caught (and therefore will cause crash) for insert/update transactions
* to ensure we are in a consistent state.
*/
object ProfileManager {
private const val TAG = "ProfileManager"
@Throws(SQLException::class)
fun createProfile(p: Profile? = null): Profile {
val profile = p ?: Profile()
profile.id = 0
try {
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)
} catch (ex: Exception) {
Log.e(TAG, "addProfile", ex)
app.track(ex)
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)
return profile
}
/**
* Note: It's caller's responsibility to update DirectBoot profile if necessary.
*/
fun updateProfile(profile: Profile): Boolean = try {
PrivateDatabase.profileDao.update(profile)
true
} catch (ex: Exception) {
Log.e(TAG, "updateProfile", ex)
app.track(ex)
false
}
@Throws(SQLException::class)
fun updateProfile(profile: Profile) = PrivateDatabase.profileDao.update(profile)
fun getProfile(id: Int): Profile? = try {
PrivateDatabase.profileDao.queryForId(id)
} catch (ex: Exception) {
} catch (ex: SQLException) {
Log.e(TAG, "getProfile", ex)
app.track(ex)
null
}
fun delProfile(id: Int): Boolean = try {
@Throws(SQLException::class)
fun delProfile(id: Int) {
PrivateDatabase.profileDao.deleteById(id)
ProfilesFragment.instance?.profilesAdapter?.removeId(id)
if (id == DataStore.profileId && DataStore.directBootAware) DirectBoot.clean()
true
} catch (ex: Exception) {
Log.e(TAG, "delProfile", ex)
app.track(ex)
false
}
fun getFirstProfile(): Profile? = try {
val result = PrivateDatabase.profileDao.query(PrivateDatabase.profileDao.queryBuilder().limit(1L).prepare())
if (result.size == 1) result[0] else null
} catch (ex: Exception) {
Log.e(TAG, "getAllProfiles", ex)
PrivateDatabase.profileDao.query(PrivateDatabase.profileDao.queryBuilder().limit(1L).prepare()).singleOrNull()
} catch (ex: SQLException) {
Log.e(TAG, "getFirstProfile", ex)
app.track(ex)
null
}
fun getAllProfiles(): List<Profile>? = try {
PrivateDatabase.profileDao.query(PrivateDatabase.profileDao.queryBuilder().orderBy("userOrder", true).prepare())
} catch (ex: Exception) {
} catch (ex: SQLException) {
Log.e(TAG, "getAllProfiles", ex)
app.track(ex)
null
@@ -21,6 +21,7 @@
package com.github.shadowsocks.plugin
import android.annotation.SuppressLint
import android.content.BroadcastReceiver
import android.content.ContentResolver
import android.content.Intent
import android.content.pm.PackageManager
@@ -84,11 +85,16 @@ object PluginManager {
""", Base64.DEFAULT))
}
private val receiver by lazy { app.listenForPackageChanges { synchronized(this) { cachedPlugins = null } } }
private var receiver: BroadcastReceiver? = null
private var cachedPlugins: Map<String, Plugin>? = null
fun fetchPlugins(): Map<String, Plugin> {
receiver
return synchronized(this) {
if (receiver == null) receiver = app.listenForPackageChanges {
synchronized(this) {
receiver = null
cachedPlugins = null
}
}
if (cachedPlugins == null) {
val pm = app.packageManager
cachedPlugins = (pm.queryIntentContentProviders(Intent(PluginContract.ACTION_NATIVE_PLUGIN),
@@ -29,6 +29,7 @@ import android.os.Bundle
import android.provider.Settings
import android.support.design.widget.BottomSheetDialog
import android.support.v7.preference.PreferenceDialogFragmentCompat
import android.support.v7.widget.DefaultItemAnimator
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
@@ -107,10 +108,12 @@ class BottomSheetPreferenceDialogFragment : PreferenceDialogFragmentCompat() {
recycler.setPadding(0, padding, 0, padding)
recycler.setHasFixedSize(true)
recycler.layoutManager = LinearLayoutManager(activity)
recycler.itemAnimator = DefaultItemAnimator()
recycler.adapter = IconListAdapter(dialog)
recycler.layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
dialog.setContentView(recycler)
dialog.findViewById<View>(R.id.touch_outside)!!.isFocusable = false
return dialog
}
@@ -52,7 +52,7 @@ object DataStore {
/**
* Setter is defined in MainActivity.onPreferenceDataStoreChanged.
*/
val directBootAware: Boolean get() = BootReceiver.enabled && (publicStore.getBoolean(Key.directBootAware) ?: false)
val directBootAware: Boolean get() = BootReceiver.enabled && publicStore.getBoolean(Key.directBootAware) == true
var serviceMode: String
get() = publicStore.getString(Key.serviceMode) ?: Key.modeVpn
set(value) = publicStore.putString(Key.serviceMode, value)
@@ -28,11 +28,7 @@ import android.util.AttributeSet
class IconListPreference(context: Context, attrs: AttributeSet? = null) : ListPreference(context, attrs) {
var entryIcons: Array<Drawable?>? = null
val selectedEntry: Int get() = entryValues.indexOf(value)
val entryIcon: Drawable? get() = try {
entryIcons?.get(selectedEntry)
} catch (_: ArrayIndexOutOfBoundsException) {
null
}
val entryIcon: Drawable? get() = entryIcons?.getOrNull(selectedEntry)
// fun setEntryIcons(@ArrayRes entryIconsResId: Int) {
// val array = getContext().getResources().obtainTypedArray(entryIconsResId)
// entryIcons = Array(array.length(), { i -> array.getDrawable(i) })
@@ -49,7 +45,7 @@ class IconListPreference(context: Context, attrs: AttributeSet? = null) : ListPr
}
init {
super.setOnPreferenceChangeListener({ preference, newValue ->
super.setOnPreferenceChangeListener { preference, newValue ->
val listener = listener
if (listener == null || listener.onPreferenceChange(preference, newValue)) {
value = newValue.toString()
@@ -57,7 +53,7 @@ class IconListPreference(context: Context, attrs: AttributeSet? = null) : ListPr
if (entryIcons != null) icon = entryIcon
true
} else false
})
}
// val a = context.obtainStyledAttributes(attrs, R.styleable.IconListPreference)
// val entryIconsResId: Int = a.getResourceId(R.styleable.IconListPreference_entryIcons, -1)
// if (entryIconsResId != -1) entryIcons = entryIconsResId
@@ -22,7 +22,6 @@ package com.github.shadowsocks.tasker
import android.app.Activity
import android.content.res.Resources
import android.os.Build
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.DefaultItemAnimator
@@ -71,13 +70,12 @@ class ConfigActivity : AppCompatActivity() {
inner class ProfilesAdapter : RecyclerView.Adapter<ProfileViewHolder>() {
internal val profiles = ProfileManager.getAllProfiles()?.toMutableList() ?: mutableListOf()
private val name = "select_dialog_singlechoice_" + (if (Build.VERSION.SDK_INT >= 21) "material" else "holo")
override fun onBindViewHolder(holder: ProfileViewHolder, position: Int) =
if (position == 0) holder.bindDefault() else holder.bind(profiles[position - 1])
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProfileViewHolder = ProfileViewHolder(
LayoutInflater.from(parent.context).inflate(Resources.getSystem()
.getIdentifier(name, "layout", "android"), parent, false))
.getIdentifier("select_dialog_singlechoice_material", "layout", "android"), parent, false))
override fun getItemCount(): Int = 1 + profiles.size
}
@@ -87,12 +85,12 @@ class ConfigActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
try {
taskerOption = Settings.fromIntent(intent!!)
} catch (_: Exception) {
val intent = intent
if (intent == null) {
finish()
return
}
taskerOption = Settings.fromIntent(intent)
setContentView(R.layout.layout_tasker)
val toolbar = findViewById<Toolbar>(R.id.toolbar)
@@ -44,8 +44,8 @@ class Settings(bundle: Bundle?) {
if (profileId >= 0) bundle.putInt(KEY_PROFILE_ID, profileId)
val profile = ProfileManager.getProfile(profileId)
return Intent().putExtra(ApiIntent.EXTRA_BUNDLE, bundle).putExtra(ApiIntent.EXTRA_STRING_BLURB,
if (profile != null)
context.getString(if (switchOn) R.string.start_service else R.string.stop_service, profile.formattedName)
if (profile != null) context.getString(if (switchOn) R.string.start_service else R.string.stop_service,
profile.formattedName)
else context.getString(if (switchOn) R.string.start_service_default else R.string.stop))
}
}
@@ -20,9 +20,7 @@
package com.github.shadowsocks.utils
import java.util.ArrayList
import java.util.Arrays
import java.util.StringTokenizer
import java.util.*
/**
* Commandline objects help handling command lines specifying processes to
@@ -86,9 +84,7 @@ object Commandline {
* @return empty string for null or no command, else every argument split
* by spaces and quoted by quoting rules.
*/
fun toString(args: Array<String>): String {
return toString(Arrays.asList(*args)) // thanks to Java, arrays aren't iterable
}
fun toString(args: Array<String>) = toString(args.asIterable()) // thanks to Java, arrays aren't iterable
/**
* Crack a command line.
@@ -7,7 +7,7 @@ import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.database.ProfileManager
import com.github.shadowsocks.preference.DataStore
import java.io.File
import java.io.FileNotFoundException
import java.io.IOException
import java.io.ObjectInputStream
import java.io.ObjectOutputStream
@@ -17,7 +17,7 @@ object DirectBoot {
fun getDeviceProfile(): Profile? = try {
ObjectInputStream(file.inputStream()).use { it.readObject() as Profile }
} catch (_: FileNotFoundException) { null }
} catch (_: IOException) { null }
fun clean() {
file.delete()
@@ -52,4 +52,5 @@ object TcpFastOpen {
"else",
" echo Failed.",
"fi"), null, true)?.joinToString("\n")
fun enabledAsync(value: Boolean) = thread { enabled(value) }.join(1000)
}
@@ -9,7 +9,6 @@ import android.support.annotation.AttrRes
import android.support.v4.app.Fragment
import android.support.v4.app.FragmentManager
import android.support.v7.util.SortedList
import android.view.accessibility.AccessibilityManager
import android.util.TypedValue
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.JniHelper
@@ -22,11 +21,6 @@ private val fieldChildFragmentManager by lazy {
field
}
fun isAccessibilityEnabled(context: Context): Boolean {
val am = context.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager
return am.isEnabled()
}
fun String.isNumericAddress() = JniHelper.parseNumericAddress(this) != null
fun String.parseNumericAddress(): InetAddress? {
val addr = JniHelper.parseNumericAddress(this)
@@ -34,8 +28,8 @@ fun String.parseNumericAddress(): InetAddress? {
}
fun parsePort(str: String?, default: Int, min: Int = 1025): Int {
val x = str?.toIntOrNull() ?: default
return if (x < min || x > 65535) default else x
val value = str?.toIntOrNull() ?: default
return if (value < min || value > 65535) default else value
}
fun broadcastReceiver(callback: (Context, Intent) -> Unit): BroadcastReceiver = object : BroadcastReceiver() {
@@ -78,6 +72,6 @@ private class SortedListIterable<out T>(private val list: SortedList<T>) : Itera
private class SortedListIterator<out T>(private val list: SortedList<T>) : Iterator<T> {
private var count = 0
override fun hasNext() = count < list.size()
override fun next(): T = list[count++]
override fun next(): T = if (hasNext()) list[count++] else throw NoSuchElementException()
}
fun <T> SortedList<T>.asIterable(): Iterable<T> = SortedListIterable(this)
@@ -1,40 +0,0 @@
/*******************************************************************************
* *
* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2017 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
package com.github.shadowsocks.widget
import android.content.Context
import android.support.design.widget.CoordinatorLayout
import android.util.AttributeSet
import android.view.View
import com.github.shadowsocks.R
class BottomMarginBehavior : CoordinatorLayout.Behavior<View> {
constructor() : super()
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
override fun layoutDependsOn(parent: CoordinatorLayout, child: View, dependency: View): Boolean =
dependency.id == R.id.stat // sorry I'm too lazy to write an attribute
override fun onDependentViewChanged(parent: CoordinatorLayout, child: View, dependency: View): Boolean {
(child.layoutParams as CoordinatorLayout.LayoutParams).bottomMargin = (parent.height - dependency.y).toInt()
return true
}
}
@@ -29,7 +29,6 @@ import android.support.graphics.drawable.AnimatedVectorDrawableCompat
import android.support.v7.widget.TooltipCompat
import android.util.AttributeSet
import android.view.View
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.R
import com.github.shadowsocks.bg.BaseService
import java.util.*
@@ -84,9 +83,7 @@ class ServiceButton @JvmOverloads constructor(context: Context, attrs: Attribute
TooltipCompat.setTooltipText(this, context.getString(R.string.connect))
}
refreshDrawableState()
isEnabled = false
if (state == BaseService.CONNECTED || state == BaseService.STOPPED) app.handler.postDelayed(
{ isEnabled = state == BaseService.CONNECTED || state == BaseService.STOPPED }, 1000)
isEnabled = state == BaseService.CONNECTED || state == BaseService.STOPPED
}
private fun counters(a: AnimatedVectorDrawableCompat, b: AnimatedVectorDrawableCompat): Boolean =
@@ -20,32 +20,27 @@
package com.github.shadowsocks.widget
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.content.Context
import android.os.Build
import android.support.design.widget.CoordinatorLayout
import android.support.design.widget.SnackbarAnimation
import android.support.design.widget.Snackbar
import android.support.v4.view.ViewCompat
import android.support.design.widget.SnackbarAnimation
import android.util.AttributeSet
import android.view.View
import com.github.shadowsocks.utils.isAccessibilityEnabled
import android.view.accessibility.AccessibilityManager
/**
* Full credits go to: https://stackoverflow.com/a/35904421/2245107
*/
class MoveUpwardBehavior : CoordinatorLayout.Behavior<View> {
constructor() : super()
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
class ShrinkUpwardBehavior(context: Context, attrs: AttributeSet) : CoordinatorLayout.Behavior<View>(context, attrs) {
private val accessibility = context.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager
override fun layoutDependsOn(parent: CoordinatorLayout, child: View, dependency: View): Boolean =
dependency is Snackbar.SnackbarLayout
dependency is Snackbar.SnackbarLayout
override fun onDependentViewChanged(parent: CoordinatorLayout, child: View, dependency: View): Boolean {
child.translationY = Math.min(0f, dependency.translationY - dependency.height)
child.layoutParams.height = dependency.y.toInt()
child.requestLayout()
return true
}
@@ -53,24 +48,18 @@ class MoveUpwardBehavior : CoordinatorLayout.Behavior<View> {
* Based on BaseTransientBottomBar.animateViewOut (support lib 27.0.2).
*/
override fun onDependentViewRemoved(parent: CoordinatorLayout, child: View, dependency: View) {
if (!isAccessibilityEnabled(parent.getContext())) {
if (accessibility.isEnabled) child.layoutParams.height = parent.height else {
val animator = ValueAnimator()
val start = child.translationY
animator.setFloatValues(start, 0f)
val start = child.height
animator.setIntValues(start, parent.height)
animator.interpolator = SnackbarAnimation.FAST_OUT_SLOW_IN_INTERPOLATOR
animator.duration = SnackbarAnimation.ANIMATION_DURATION
animator.addUpdateListener(object : ValueAnimator.AnimatorUpdateListener {
private var previousValue = start
override fun onAnimationUpdate(animator: ValueAnimator) {
val currentValue = animator.animatedValue as Float
child.translationY = currentValue
previousValue = currentValue
}
})
@Suppress("NAME_SHADOWING")
animator.addUpdateListener { animator ->
child.layoutParams.height = animator.animatedValue as Int
child.requestLayout()
}
animator.start()
} else {
child.translationY = 0f
}
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 198 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 400 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 156 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 298 B

@@ -1,25 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="@color/material_primary_300" />
</shape>
</item>
<item>
<selector android:enterFadeDuration="@android:integer/config_mediumAnimTime"
android:exitFadeDuration="@android:integer/config_mediumAnimTime">
<item android:state_selected="true">
<shape android:shape="rectangle">
<solid android:color="@color/material_green_700" />
</shape>
</item>
</selector>
</item>
<!-- android:start not available until API 23 -->
<item android:left="8dp">
<shape android:shape="rectangle">
<solid android:color="@android:color/white" />
</shape>
</item>
<item android:drawable="?android:attr/selectableItemBackground"/>
</layer-list>
@@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="?android:colorControlHighlight">
<item>
<selector android:enterFadeDuration="@android:integer/config_mediumAnimTime"
android:exitFadeDuration="@android:integer/config_mediumAnimTime">
<item android:state_selected="true">
<color android:color="@color/material_primary_100"/>
</item>
</selector>
</item>
</ripple>
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="?android:colorControlHighlight">
<item>
<color android:color="@color/material_primary_300"/>
</item>
</ripple>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 228 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 510 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 284 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 748 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 364 B

@@ -15,18 +15,11 @@
</item>
</selector>
</item>
<item android:left="@dimen/profile_padding">
<!-- android:start not available until API 23 -->
<item android:left="8dp">
<shape android:shape="rectangle">
<solid android:color="@android:color/white" />
</shape>
</item>
<item>
<selector>
<item android:state_pressed="true">
<shape android:shape="rectangle">
<solid android:color="#12000000" />
</shape>
</item>
</selector>
</item>
<item android:drawable="?android:attr/selectableItemBackground"/>
</layer-list>
@@ -1,8 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"
android:enterFadeDuration="@android:integer/config_mediumAnimTime"
android:exitFadeDuration="@android:integer/config_mediumAnimTime">
<item android:state_selected="true">
<color android:color="@color/material_primary_100"/>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<selector android:enterFadeDuration="@android:integer/config_mediumAnimTime"
android:exitFadeDuration="@android:integer/config_mediumAnimTime">
<item android:state_selected="true">
<color android:color="@color/material_primary_100"/>
</item>
</selector>
</item>
</selector>
<item android:drawable="?android:attr/selectableItemBackground"/>
</layer-list>
@@ -1,17 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="@color/material_primary_300" />
</shape>
</item>
<item>
<selector>
<item android:state_pressed="true">
<shape android:shape="rectangle">
<solid android:color="#12000000" />
</shape>
</item>
</selector>
<color android:color="@color/material_primary_300"/>
</item>
<item android:drawable="?android:attr/selectableItemBackground"/>
</layer-list>
@@ -2,7 +2,8 @@
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
android:viewportHeight="24.0"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"/>
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="#FF000000"
android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z"/>
</vector>
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:fillColor="#f5f5f5"
android:pathData="M24,24m-22,0a22,22 0,1 1,44 0a22,22 0,1 1,-44 0Z" />
<path
android:fillColor="#546e7a"
android:pathData="M24,21.8 C25.7673,21.8,27.2,23.2327,27.2,25 C27.2,26.7673,25.7673,28.2,24,28.2
C22.2327,28.2,20.8,26.7673,20.8,25 C20.8,23.2327,22.2327,21.8,24,21.8 Z" />
<path
android:fillColor="#546e7a"
android:pathData="M21,15 L19.17,17 L16,17 A2,2,0,0,0,14,19 L14,31 A2,2,0,0,0,16,33 L32,33
A2,2,0,0,0,34,31 L34,19 A2,2,0,0,0,32,17 L28.83,17 L27,15 Z M24,30
A5,5,0,1,1,29,25 A5,5,0,0,1,24,30 Z" />
</vector>
@@ -2,9 +2,7 @@
<animated-vector
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
xmlns:tools="http://schemas.android.com/tools"
android:drawable="@drawable/ic_service_busy"
tools:ignore="NewApi">
android:drawable="@drawable/ic_service_busy">
<target android:name="path">
<aapt:attr name="android:animation">
<objectAnimator
@@ -2,9 +2,7 @@
<animated-vector
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
xmlns:tools="http://schemas.android.com/tools"
android:drawable="@drawable/ic_service_idle"
tools:ignore="NewApi">
android:drawable="@drawable/ic_service_idle">
<target android:name="strike_thru_path">
<aapt:attr name="android:animation">
<objectAnimator
@@ -2,9 +2,7 @@
<animated-vector
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
xmlns:tools="http://schemas.android.com/tools"
android:drawable="@drawable/ic_service_idle"
tools:ignore="NewApi">
android:drawable="@drawable/ic_service_idle">
<target android:name="strike_thru_path">
<aapt:attr name="android:animation">
<objectAnimator
@@ -2,9 +2,7 @@
<animated-vector
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
xmlns:tools="http://schemas.android.com/tools"
android:drawable="@drawable/ic_service_busy"
tools:ignore="NewApi">
android:drawable="@drawable/ic_service_busy">
<target android:name="path">
<aapt:attr name="android:animation">
<objectAnimator
+12 -4
View File
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
@@ -8,8 +9,15 @@
android:layout_height="wrap_content"
android:labelFor="@+id/content"
android:entries="@array/acl_rule_templates"/>
<EditText android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textNoSuggestions|textMultiLine"/>
<android.support.design.widget.TextInputLayout
android:id="@+id/content_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:errorEnabled="true">
<android.support.design.widget.TextInputEditText
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textNoSuggestions|textMultiLine"/>
</android.support.design.widget.TextInputLayout>
</LinearLayout>
+34 -26
View File
@@ -3,8 +3,8 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:duplicateParentState="false">
<include layout="@layout/toolbar_light_dark"/>
@@ -41,28 +41,36 @@
android:textColor="?android:attr/textColorSecondary"
android:textSize="18sp"/>
</LinearLayout>
<FrameLayout android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_weight="1">
<ProgressBar android:id="@+id/loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<android.support.v7.widget.RecyclerView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="@+id/list"
tools:listitem="@layout/layout_apps_item"/>
<com.futuremind.recyclerviewfastscroll.FastScroller
android:id="@+id/fastscroller"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="end"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
app:fastscroll__handleColor="@color/material_primary_500"
app:fastscroll__bubbleColor="@color/material_primary_500"
app:fastscroll__bubbleTextAppearance="@style/StyledScrollerTextAppearance"/>
</FrameLayout>
<android.support.design.widget.CoordinatorLayout
android:id="@+id/snackbar"
android:background="@color/material_primary_700"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<FrameLayout android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:attr/windowBackground"
app:layout_behavior="com.github.shadowsocks.widget.ShrinkUpwardBehavior">
<ProgressBar android:id="@+id/loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<android.support.v7.widget.RecyclerView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="@+id/list"
tools:listitem="@layout/layout_apps_item"/>
<com.futuremind.recyclerviewfastscroll.FastScroller
android:id="@+id/fastscroller"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="end"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
app:fastscroll__handleColor="@color/material_primary_500"
app:fastscroll__bubbleColor="@color/material_primary_500"
app:fastscroll__bubbleTextAppearance="@style/StyledScrollerTextAppearance"/>
</FrameLayout>
</android.support.design.widget.CoordinatorLayout>
</LinearLayout>
+118 -112
View File
@@ -9,140 +9,146 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/fragment_holder"
android:background="?android:attr/windowBackground"
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent" />
android:layout_height="match_parent"
app:layout_behavior="com.github.shadowsocks.widget.ShrinkUpwardBehavior">
<LinearLayout
android:id="@+id/stat"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/background_stat"
android:elevation="4dp"
android:focusable="true"
android:nextFocusRight="@+id/fab"
android:orientation="vertical"
android:paddingBottom="16dip"
android:paddingEnd="16dip"
android:paddingStart="16dip"
android:paddingTop="16dip"
android:layout_gravity="bottom"
app:layout_behavior="com.github.shadowsocks.widget.MoveUpwardBehavior">
<FrameLayout
android:id="@+id/fragment_holder"
android:background="?android:attr/windowBackground"
android:layout_above="@+id/stat"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<TextView
android:id="@+id/status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:paddingEnd="72dip"
android:textColor="?android:attr/textColorPrimary"
android:textSize="16sp"
tools:ignore="RtlSymmetry"/>
<com.github.shadowsocks.widget.BoundedGridLayout
<LinearLayout
android:id="@+id/stat"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:bounded_width="288dp"
app:columnCount="4">
<TextView
style="@style/TextAppearance.AppCompat.Body2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="24dp"
android:ellipsize="marquee"
android:gravity="end"
android:text="@string/sent"
app:layout_column="0"/>
<TextView
android:id="@+id/tx"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="fill_horizontal"
android:ellipsize="marquee"
android:textColor="?android:attr/textColorSecondary"
android:textSize="14sp"
app:layout_column="1"
app:layout_columnWeight="1"/>
android:background="@drawable/background_stat"
android:elevation="4dp"
android:focusable="true"
android:nextFocusRight="@+id/fab"
android:orientation="vertical"
android:paddingBottom="16dip"
android:paddingEnd="16dip"
android:paddingStart="16dip"
android:paddingTop="16dip"
android:layout_alignParentBottom="true">
<TextView
android:id="@+id/status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:text="▲"
android:textColor="?android:attr/textColorSecondary"
android:textSize="14sp"
app:layout_column="2"/>
android:paddingEnd="72dip"
android:textColor="?android:attr/textColorPrimary"
android:textSize="16sp"
tools:ignore="RtlSymmetry"/>
<TextView
android:id="@+id/txRate"
android:layout_width="0dp"
<com.github.shadowsocks.widget.BoundedGridLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="fill_horizontal"
android:ellipsize="marquee"
android:gravity="end"
android:textColor="?android:attr/textColorSecondary"
android:textSize="14sp"
app:layout_column="3"
app:layout_columnWeight="1"/>
app:bounded_width="288dp"
app:columnCount="4">
<TextView
style="@style/TextAppearance.AppCompat.Body2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="24dp"
android:ellipsize="marquee"
android:gravity="end"
android:text="@string/received"
app:layout_column="0"
app:layout_row="1"/>
<TextView
style="@style/TextAppearance.AppCompat.Body2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="24dp"
android:ellipsize="marquee"
android:gravity="end"
android:text="@string/sent"
app:layout_column="0"/>
<TextView
android:id="@+id/rx"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="fill_horizontal"
android:ellipsize="marquee"
android:textColor="?android:attr/textColorSecondary"
android:textSize="14sp"
app:layout_column="1"
app:layout_columnWeight="1"
app:layout_row="1"/>
<TextView
android:id="@+id/tx"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="fill_horizontal"
android:ellipsize="marquee"
android:textColor="?android:attr/textColorSecondary"
android:textSize="14sp"
app:layout_column="1"
app:layout_columnWeight="1"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:text="▼"
android:textColor="?android:attr/textColorSecondary"
android:textSize="14sp"
app:layout_column="2"
app:layout_row="1"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:text="▲"
android:textColor="?android:attr/textColorSecondary"
android:textSize="14sp"
app:layout_column="2"/>
<TextView
android:id="@+id/rxRate"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="fill_horizontal"
android:ellipsize="marquee"
android:gravity="end"
android:textColor="?android:attr/textColorSecondary"
android:textSize="14sp"
app:layout_column="3"
app:layout_columnWeight="1"
app:layout_row="1"/>
</com.github.shadowsocks.widget.BoundedGridLayout>
</LinearLayout>
<TextView
android:id="@+id/txRate"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="fill_horizontal"
android:ellipsize="marquee"
android:gravity="end"
android:textColor="?android:attr/textColorSecondary"
android:textSize="14sp"
app:layout_column="3"
app:layout_columnWeight="1"/>
<TextView
style="@style/TextAppearance.AppCompat.Body2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="24dp"
android:ellipsize="marquee"
android:gravity="end"
android:text="@string/received"
app:layout_column="0"
app:layout_row="1"/>
<TextView
android:id="@+id/rx"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="fill_horizontal"
android:ellipsize="marquee"
android:textColor="?android:attr/textColorSecondary"
android:textSize="14sp"
app:layout_column="1"
app:layout_columnWeight="1"
app:layout_row="1"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:text="▼"
android:textColor="?android:attr/textColorSecondary"
android:textSize="14sp"
app:layout_column="2"
app:layout_row="1"/>
<TextView
android:id="@+id/rxRate"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="fill_horizontal"
android:ellipsize="marquee"
android:gravity="end"
android:textColor="?android:attr/textColorSecondary"
android:textSize="14sp"
app:layout_column="3"
app:layout_columnWeight="1"
app:layout_row="1"/>
</com.github.shadowsocks.widget.BoundedGridLayout>
</LinearLayout>
</RelativeLayout>
<!-- workaround for 0 elevation in disabled state -->
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginTop="@dimen/fab_margin_top"
android:layout_marginTop="-28dp"
android:elevation="6dp"
app:layout_anchor="@id/stat"
app:layout_anchorGravity="top|end">
@@ -1,12 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include layout="@layout/toolbar_light_dark" />
<me.dm7.barcodescanner.zxing.ZXingScannerView android:id="@+id/scanner"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
<fragment
android:id="@+id/barcode"
android:name="com.google.android.gms.samples.vision.barcodereader.BarcodeCapture"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
app:gvb_auto_focus="true"
app:gvb_code_format="qr_code"/>
</LinearLayout>
@@ -1,6 +1,6 @@
<menu xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/action_apply_all"
android:title="@string/action_apply_all"
android:alphabeticShortcut="a"
@@ -1,41 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/selection"
android:alphabeticShortcut="s"
android:icon="?attr/actionModeSelectAllDrawable"
android:numericShortcut="0"
android:title="@string/action_selection"
android:visible="false"
app:showAsAction="always">
<menu>
<item
android:id="@+id/action_select_all"
android:alphabeticShortcut="a"
android:numericShortcut="1"
android:title="@android:string/selectAll"
app:showAsAction="always"/>
<item
android:id="@+id/action_cut"
android:alphabeticShortcut="t"
android:numericShortcut="2"
android:title="@android:string/cut"
app:showAsAction="always"/>
<item
android:id="@+id/action_copy"
android:alphabeticShortcut="c"
android:numericShortcut="3"
android:title="@android:string/copy"
app:showAsAction="always"/>
<item
android:id="@+id/action_delete"
android:alphabeticShortcut="d"
android:numericShortcut="4"
android:title="@string/delete"
app:showAsAction="always"/>
</menu>
</item>
<item android:title="@string/action_add_rule"
android:icon="@drawable/ic_av_playlist_add"
android:alphabeticShortcut="a"
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:ignore="AppCompatResource">
<item
android:id="@+id/action_select_all"
android:alphabeticShortcut="a"
android:icon="?attr/actionModeSelectAllDrawable"
android:numericShortcut="1"
android:title="@android:string/selectAll"
android:showAsAction="always"/>
<item
android:id="@+id/action_cut"
android:alphabeticShortcut="t"
android:icon="?attr/actionModeCutDrawable"
android:numericShortcut="2"
android:title="@android:string/cut"
android:showAsAction="always"/>
<item
android:id="@+id/action_copy"
android:alphabeticShortcut="c"
android:icon="?attr/actionModeCopyDrawable"
android:numericShortcut="3"
android:title="@android:string/copy"
android:showAsAction="always"/>
<item
android:id="@+id/action_delete"
android:alphabeticShortcut="d"
android:icon="@drawable/ic_action_delete"
android:numericShortcut="4"
android:title="@string/delete"
android:showAsAction="always"/>
</menu>
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/action_import"
android:title="@string/action_import_file"
android:icon="@drawable/ic_image_photo"
android:alphabeticShortcut="p"
android:numericShortcut="1"
app:showAsAction="always"/>
</menu>
+3 -5
View File
@@ -4,7 +4,7 @@
<!-- misc -->
<string name="profile">"پروفایل"</string>
<string name="connection_test_testing">"در حال آزمایش..."</string>
<string name="connection_test_testing">"در حال آزمایش…"</string>
<!-- proxy category -->
<string name="profile_name">"نام پروفایل"</string>
@@ -20,7 +20,7 @@
<string name="invalid_server">"آدرس سرور نامعتبر است"</string>
<string name="service_failed">"اتصال به سرور ناموفق بود!"</string>
<string name="stop">"غیرفعال‌کردن"</string>
<string name="stopping">"در حال قطع‌کردن اتصال..."</string>
<string name="stopping">"در حال قطع‌کردن اتصال…"</string>
<string name="profile_invalid_input">"هیچ پروفایل معتبری یافت نشد!"</string>
<!-- alert category -->
@@ -33,7 +33,6 @@
<string name="profiles">"پروفایل‌ها"</string>
<string name="settings">"تنظیمات"</string>
<string name="faq">"سوالات مداول"</string>
<string name="recovery">"ریست"</string>
<string name="about">"درباره ما"</string>
<string name="edit">"ویرایش"</string>
<string name="share">"به اشتراک گذاری"</string>
@@ -52,9 +51,8 @@
<!-- status -->
<string name="sent">"فرستاده‌شده:"</string>
<string name="received">"دریافت‌شده:"</string>
<string name="connecting">"در حال اتصال..."</string>
<string name="connecting">"در حال اتصال…"</string>
<string name="vpn_connected">"وصل شد. برای بررسی اتصال ضربه (Tap) بزنید."</string>
<string name="nat_connected">"اتصال برقرار است."</string>
<string name="not_connected">"اتصال برقرار نیست!"</string>
<string name="route_entry_all">"همه"</string>
</resources>
+35
View File
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- misc -->
<string name="profile">"Profil"</string>
<string name="connection_test_error">"Impossible de détecter la connexion Internet :%s"</string>
<string name="connection_test_fail">"Internet Indisponible"</string>
<string name="connection_test_error_status_code">"Code de l'Erreur : #%d"</string>
<string name="proxy">"Serveur"</string>
<string name="sitekey">"Mot de passe"</string>
<string name="service_failed">"Échec de la connexion au serveur distant"</string>
<!-- alert category -->
<string name="close">"Fermer"</string>
<string name="profile_empty">"Veuillez sélectionner un profil"</string>
<!-- menu category -->
<string name="profiles">"Profils"</string>
<string name="settings">"Paramètres"</string>
<string name="about">"À propos"</string>
<string name="share">"Partager"</string>
<string name="add_profile">"Ajouter un Profil"</string>
<string name="action_apply_all">"Appliquer les Paramètres à tous les Profils"</string>
<string name="action_export">"Exporter vers le presse-papiers"</string>
<string name="action_import">"Importer depuis le presse-papiers"</string>
<string name="delete">"Supprimer"</string>
<string name="delete_confirm_prompt">"Êtes-vous sûr de vouloir supprimer ce profil?"</string>
<string name="share_qr_nfc">"QR Code/NFC"</string>
<string name="add_profile_methods_scan_qr_code">"Scanner le QR Code"</string>
<plurals name="removed">
<item quantity="one">"Supprimé"</item>
<item quantity="other">"%d éléments supprimés"</item>
</plurals>
<string name="undo">"Annuler"</string>
</resources>

Some files were not shown because too many files have changed in this diff Show More