Compare commits

..
36 Commits
Author SHA1 Message Date
Max Lv 71b1d5fef1 Bump version 2016-06-05 15:08:03 +08:00
Mygod 5afb8b9cd4 Fix connection test disappearing 2016-06-05 12:20:48 +08:00
Mygod 650a01c9ce Update dependencies 2016-06-05 12:06:57 +08:00
Mygod 07798c7e56 Fix imported per-app proxy settings not updating UI 2016-06-05 12:06:27 +08:00
Mygod 8c3b4cc021 Refine #701 2016-06-05 11:46:04 +08:00
Ilya 759d812347 Add russian localosation (#701)
Created russian localisation file shadowsocks-android/src/main/res/values-ru/strings.xml
2016-06-01 03:06:32 -05:00
Mygod 2d4d5edff5 Update dependencies 2016-05-30 09:14:06 +08:00
Mygod 86f32ea67c Add app.isNatEnabled for performance and readability
Warning: This is a boring commit.
2016-05-24 11:52:06 +08:00
Mygod 461a0bda3f Remove unnecessary app.settings param in ConfigUtils.load 2016-05-24 11:46:31 +08:00
Mygod c878d4daa6 Fix DeathRecipient leaking bug 2016-05-24 11:42:12 +08:00
Mygod 9be24d7408 Use SharedPreference.Editor.apply instead of commit 2016-05-24 11:39:43 +08:00
Mygod 28ea9dd807 Use app.* instead of ShadowsocksApplication.*
Because typing ShadowsocksApplication is quite troublesome.
2016-05-24 11:37:33 +08:00
Mygod c695847403 Remove package object 2016-05-24 11:23:46 +08:00
Mygod 4a1f6fea98 Remove launcher.sh 2016-05-24 11:13:03 +08:00
Mygod 535561cfe4 Manage auto connect via Android API 2016-05-24 10:53:34 +08:00
Mygod 505e40eb3e Integrate bash scripts into sbt (#692) 2016-05-23 16:42:46 +08:00
Mygod a1871f792d Fix building 2016-05-21 17:51:10 +08:00
Mygod 0b7fb75b7a Obsolete code cleanup 2016-05-20 21:33:01 +08:00
Mygod c52c33f3ac Update CONTRIBUTING.md 2016-05-20 20:26:58 +08:00
CzBiX 6cdb12de99 Create CONTRIBUTING.md (#690) 2016-05-20 18:14:23 +08:00
Mygod 5d902be580 Reduce proguard output 2016-05-20 11:14:33 +08:00
Mygod 14267260bf Update dependencies 2016-05-19 16:54:15 +08:00
WangXinXin 5e0eed2953 Update README.md (#686) 2016-05-18 15:35:50 +08:00
Syrone Wong 8310e13be3 add a perl script to generate chnroute (#676)
The perl version takes two seconds while the python version takes more than 30 seconds

Usage: perl ./gen.pl < chnroute.txt > chnroute-merged.txt

Signed-off-by: Syrone Wong <wong.syrone@gmail.com>
2016-05-07 08:29:15 +08:00
Syrone Wong 6b91d44c1c update chnroute (#675)
Data source:

http://ftp.afrinic.net/pub/stats/afrinic/delegated-afrinic-extended-latest
http://ftp.apnic.net/apnic/stats/apnic/delegated-apnic-extended-latest
http://ftp.arin.net/pub/stats/arin/delegated-arin-extended-latest
http://ftp.lacnic.net/pub/stats/lacnic/delegated-lacnic-extended-latest
http://ftp.ripe.net/pub/stats/ripencc/delegated-ripencc-extended-latest
https://www.ipip.net/
2016-05-07 08:29:05 +08:00
Max Lv 883b6349ab Fix #673 2016-05-06 09:02:47 +08:00
Mygod 2c2e5790f5 Fix typo: deattach => detach
I can't believe I've been so blind.
2016-05-05 20:04:03 +08:00
Mygod 4168329481 Update sbt-android 2016-05-05 19:27:25 +08:00
Mygod 19be10fbdf Update proguard 2016-05-04 22:47:14 +08:00
Max Lv 70aa867c53 Fix a race condition 2016-05-04 08:58:46 +08:00
Mygod f4b18b6145 Use Chinese (Taiwan) translation as fallback for other Chinese locales (#666) 2016-04-30 16:20:34 +08:00
LNDDYL 7845b7abdc Fix traditional Chinese translation 2016-04-29 18:44:51 +08:00
Max Lv 5328f5851d Fix #658 2016-04-25 20:24:55 +08:00
Mygod 1ddde094e3 Fix a crash in Parser 2016-04-25 13:12:06 +08:00
Mygod ee7ff7d5dd Use close button in ProfileManagerActivity
Because putting down arrow and back arrow together doesn't make sense.
2016-04-25 13:03:16 +08:00
Mygod 0503eeac41 Refine onServiceDisconnected 2016-04-25 12:53:38 +08:00
46 changed files with 978 additions and 686 deletions
+2 -3
View File
@@ -13,7 +13,7 @@ addons:
- libncurses5:i386
scala:
- 2.11.7
- 2.11.8
before_cache:
- find $HOME/.sbt -name "*.lock" | xargs rm
@@ -34,5 +34,4 @@ before_install:
- cp local.properties.travis local.properties
script:
- ./build.sh > /dev/null
- sbt android:package-release
- sbt native-build android:package-release
+1
View File
@@ -0,0 +1 @@
Please check the [FAQ](https://github.com/shadowsocks/shadowsocks-android/wiki/FAQ) before submitting new issues.
+2 -5
View File
@@ -19,19 +19,16 @@ A [shadowsocks](http://shadowsocks.org) client for Android, written in Scala.
* Set environment variable `ANDROID_HOME` to `/path/to/android-sdk`
* Set environment variable `ANDROID_NDK_HOME` to `/path/to/android-ndk`
* Create your key following the instructions at http://developer.android.com/guide/publishing/app-signing.html#cert
* Create your key following the instructions at https://developer.android.com/studio/publish/app-signing.html
* Put your key in ~/.keystore
* Create `local.properties` from `local.properties.example` with your own key information
* Invoke the building like this
```bash
git submodule update --init
# Build native binaries
./build.sh
# Build the App
sbt clean android:package-release
sbt native-build clean android:package-release
```
#### BUILD on Mac OS X (with HomeBrew)
+17 -8
View File
@@ -29,24 +29,33 @@ resolvers += "JRAF" at "http://JRAF.org/static/maven/2"
libraryDependencies ++= Seq(
"dnsjava" % "dnsjava" % "2.1.7",
"com.github.kevinsawicki" % "http-request" % "6.0",
"commons-net" % "commons-net" % "3.4",
"eu.chainfire" % "libsuperuser" % "1.0.0.201602271131",
"com.google.zxing" % "android-integration" % "3.2.1",
"net.glxn.qrgen" % "android" % "2.0",
"com.google.android.gms" % "play-services-base" % "8.4.0",
"com.google.android.gms" % "play-services-ads" % "8.4.0",
"com.google.android.gms" % "play-services-analytics" % "8.4.0",
"com.android.support" % "design" % "23.3.0",
"com.android.support" % "gridlayout-v7" % "23.3.0",
"com.android.support" % "cardview-v7" % "23.3.0",
"com.google.android.gms" % "play-services-base" % "9.0.2",
"com.google.android.gms" % "play-services-ads" % "9.0.2",
"com.google.android.gms" % "play-services-analytics" % "9.0.2",
"com.android.support" % "design" % "23.4.0",
"com.android.support" % "gridlayout-v7" % "23.4.0",
"com.android.support" % "cardview-v7" % "23.4.0",
"com.github.jorgecastilloprz" % "fabprogresscircle" % "1.01",
"com.j256.ormlite" % "ormlite-core" % "4.48",
"com.j256.ormlite" % "ormlite-android" % "4.48",
"com.twofortyfouram" % "android-plugin-api-for-locale" % "1.0.2",
"com.github.clans" % "fab" % "1.6.3"
"com.github.clans" % "fab" % "1.6.4"
)
proguardVersion in Android := "5.2.1"
proguardOptions in Android ++= Seq("-keep class com.github.shadowsocks.** { <init>(...); }",
"-keep class com.github.shadowsocks.System { *; }",
"-keepattributes *Annotation*",
"-dontnote com.j256.ormlite.**",
"-dontnote org.xbill.**",
"-dontwarn org.xbill.**")
lazy val nativeBuild = TaskKey[Unit]("native-build", "Build native executables")
nativeBuild := {
"./build.sh" !
}
Executable
+12
View File
@@ -0,0 +1,12 @@
#!/usr/bin/env perl
## ArchLinux install package via pacman: perl-net-cidr-lite
use strict;
use warnings;
use Net::CIDR::Lite;
my $cidr = Net::CIDR::Lite->new;
while (my $line=<>) {
$cidr->add($line);
}
foreach my $line( @{$cidr->list} ) {
print "<item>$line</item>\n";
}
-5
View File
@@ -1,5 +0,0 @@
#!/bin/bash
convert shadow.png -resize 48x48 src/main/res/drawable-mdpi/ic_launcher.png
convert shadow.png -resize 72x72 src/main/res/drawable-hdpi/ic_launcher.png
convert shadow.png -resize 96x96 src/main/res/drawable-xhdpi/ic_launcher.png
convert shadow.png -resize 144x144 src/main/res/drawable-xxhdpi/ic_launcher.png
+1 -1
View File
@@ -1,3 +1,3 @@
addSbtPlugin("org.scala-android" % "sbt-android" % "1.6.0")
addSbtPlugin("org.scala-android" % "sbt-android" % "1.6.3")
addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.1.10")
+3 -4
View File
@@ -1,7 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" package="com.github.shadowsocks"
android:versionCode="136"
android:versionName="2.10.3">
android:versionCode="137"
android:versionName="2.10.4">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
@@ -125,10 +125,9 @@
</intent-filter>
</service>
<receiver android:name=".ShadowsocksReceiver">
<receiver android:name=".BootReceiver" android:enabled="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="com.github.shadowsocks.ACTION_UPDATE_STATE"/>
</intent-filter>
</receiver>
@@ -4,7 +4,6 @@ import com.github.shadowsocks.aidl.Config;
import com.github.shadowsocks.aidl.IShadowsocksServiceCallback;
interface IShadowsocksService {
int getMode();
int getState();
oneway void registerCallback(IShadowsocksServiceCallback cb);
+2
View File
@@ -386,6 +386,7 @@ LOCAL_MODULE := ss-local
LOCAL_SRC_FILES := $(addprefix shadowsocks-libev/src/, $(SHADOWSOCKS_SOURCES))
LOCAL_CFLAGS := -Wall -O2 -fno-strict-aliasing -DMODULE_LOCAL \
-DUSE_CRYPTO_OPENSSL -DANDROID -DHAVE_CONFIG_H \
-DCONNECT_IN_PROGRESS=EINPROGRESS \
-I$(LOCAL_PATH)/include \
-I$(LOCAL_PATH)/libev \
-I$(LOCAL_PATH)/libancillary \
@@ -414,6 +415,7 @@ LOCAL_MODULE := ss-tunnel
LOCAL_SRC_FILES := $(addprefix shadowsocks-libev/src/, $(SHADOWSOCKS_SOURCES))
LOCAL_CFLAGS := -Wall -O2 -fno-strict-aliasing -DMODULE_TUNNEL \
-DUSE_CRYPTO_OPENSSL -DANDROID -DHAVE_CONFIG_H -DSSTUNNEL_JNI \
-DCONNECT_IN_PROGRESS=EINPROGRESS \
-I$(LOCAL_PATH)/libev \
-I$(LOCAL_PATH)/libancillary \
-I$(LOCAL_PATH)/include \
+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="proxied_apps_summary">Установить прокси для выбранных приложений</string>
</resources>
+134
View File
@@ -0,0 +1,134 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- global/misc category -->
<string name="misc_cat">Разное</string>
<string name="profile">Профиль</string>
<string name="profile_summary">Переключить на другой профиль или добавить новые</string>
<string name="nat">Режим NAT (не рекомендуется)</string>
<string name="nat_summary">Использовать режим NAT вместо VPN. Требует ROOT прав.</string>
<string name="stat">Сетвая Активность</string>
<string name="stat_summary">Отправлено: \t\t\t\t\t%3$s\t↑\t%1$s/s\nПолучено: \t%4$s\t↓\t%2$s/s</string>
<string name="stat_profiles">\n%1$s↑\t%2$s↓</string>
<string name="traffic_summary">%1$s/s↑\t%2$s/s↓</string>
<plurals name="bytes">
<item quantity="one">Byte</item>
<item quantity="other">Bytes</item>
</plurals>
<string name="connection_test_pending">Проверить подключение</string>
<string name="connection_test_testing">Проверка…</string>
<string name="connection_test_available">Успешно: %dms задержка</string>
<string name="connection_test_error">Сбой проверки интернет подключения: %s</string>
<string name="connection_test_fail">Интернет Недоступен</string>
<string name="connection_test_error_status_code">Код ошибки: #%d</string>
<!-- proxy category -->
<string name="proxy_cat">Настройки Сервера</string>
<string name="profile_name">Имя Профиля</string>
<string name="proxy">Сервер</string>
<string name="proxy_summary">Имя или IP удалённого сервера</string>
<string name="remote_port">Удалённый порт</string>
<string name="remote_port_summary">%d (номер порта удалённого сервера)</string>
<string name="port">Локальный порт</string>
<string name="port_summary">%d (номер порта локального сервера)</string>
<string name="sitekey">Пароль</string>
<string name="sitekey_summary">Пароль удалённого сервера</string>
<string name="enc_method">Метод Шифрования</string>
<!-- feature category -->
<string name="feature_cat">Дополнительные Настройки</string>
<string name="ipv6">IPv6 Маршрут</string>
<string name="ipv6_summary">Перенаправлять IPv6 траффик на удалённый сервер</string>
<string name="onetime_auth">Единоразовая Аутентификация</string>
<string name="onetime_auth_summary">Включить единоразовую аутентификацию</string>
<string name="http_proxy">HTTP Прокси</string>
<string name="http_proxy_summary">Локальный HTTP прокси</string>
<string name="dns_proxy">DNS Прокси</string>
<string name="dns_proxy_summary">Перенаправлять DNS запросы на удалённый сервер</string>
<string name="auto_set_gfwlist">CHN Маршрут</string>
<string name="auto_set_gfwlist_summary">Игнорировать все сайты находящиеся в Китае</string>
<string name="route_list">Маршрут</string>
<string name="proxied_apps">Прокси для выбранных приложений</string>
<string name="proxied_apps_summary">Установить прокси для выбранных приложений, требует режим NAT под Android 4.x</string>
<string name="on">Вкл</string>
<string name="bypass_apps">В обход прокси</string>
<string name="bypass_apps_summary">Включите эту опцию для работы выбранных приложений в обход прокси</string>
<string name="auto_connect">Авто Подключение</string>
<string name="auto_connect_summary">Запускать Shadowsocks при включении</string>
<string name="udp_dns">Проброс UDP</string>
<string name="udp_dns_summary">Пробрасывать UDP пакеты на удалённый сервер</string>
<!-- notification category -->
<string name="forward_success">Shadowsocks запущен.</string>
<string name="invalid_server">Неправильное имя сервера</string>
<string name="service_failed">Ошибка при подключении к удалённому серверу</string>
<string name="nat_deprecated">ВНИМАНИЕ:режим NAT не работает в Android 5.0</string>
<string name="nat_no_root">Режим NAT требует наличия ROOT прав</string>
<string name="switch_to_vpn">Переключить на режим VPN</string>
<string name="stop">Остановить</string>
<string name="stopping">Останавливается…</string>
<string name="vpn_error">%s</string>
<string name="reboot_required">Сбой запуска VPN сервиса. Требуется перезагрузка Вашего устройства.</string>
<string name="quick_switch">Переключить</string>
<!-- alert category -->
<string name="close">Закрыть</string>
<string name="proxy_empty">Прокси/Пароль не должны быть пустыми</string>
<string name="connect">Подключить</string>
<string name="initializing">Инициализация…</string>
<string name="recovering">Сброс…</string>
<string name="loading">Загрузка…</string>
<string name="remove_profile">Удалить этот профиль "%s"?</string>
<!-- menu category -->
<string name="profiles">Профили</string>
<string name="settings">Настройки</string>
<string name="recovery">Сброс</string>
<string name="recovery_summary">Сброс всех фоновых задач</string>
<string name="about">О приложении</string>
<string name="about_title">Shadowsocks %s</string>
<string name="share">Поделиться</string>
<string name="share_message">Сканировать QR код или использовать Android Beam (NFC)</string>
<string name="share_message_without_nfc">Сканировать QR код</string>
<string name="share_message_nfc_disabled">Сканировать QR код\nAndroid Beam отключен</string>
<string name="turn_on_nfc">Включить NFC &amp; Android Beam</string>
<string name="flush_dnscache">Очистить кэш DNS</string>
<string name="flush_dnscache_summary">Работает только в режиме NAT</string>
<string name="flush_dnscache_summary_root">Работает только в режиме NAT. Требует наличия ROOT прав.</string>
<string name="flush_dnscache_no_root">Очистка кэша DNS не работает без налиичя ROOT прав</string>
<string name="flushing">Очистка</string>
<string name="add_profile">Добавить Профиль</string>
<string name="action_export">Экспортировать в буфер обмена</string>
<string name="action_import">Импортировать из буфера обмена</string>
<string name="action_export_msg">Успешный экспорт!</string>
<string name="action_export_err">Сбой экспорта.</string>
<string name="action_import_msg">Успешный импорт!</string>
<string name="action_import_err">Сбой импорта.</string>
<!-- profile -->
<string name="profile_manager_dialog">Profiles</string>
<string name="profile_manager_dialog_content">&#8226; Нажмите для выбора профиля;\n&#8226; Сдвиньте влево/вправо или нажмите влево
чтобы удалить;\n&#8226; Подержите чтобы изменить порядок профилей.</string>
<string name="gotcha">Gotcha!</string>
<string name="add_profile_dialog">Добавить этот Профиль Shadowsocks?</string>
<string name="add_profile_methods_scan_qr_code">Сканировать QR код</string>
<string name="add_profile_methods_manual_settings">Настройка вручную</string>
<string name="add_profile_nfc_hint_title">Использовать Android Beam (NFC) для добавления профиля</string>
<string name="add_profile_nfc_hint">Оставьте экран включённым на обоих устройствах, на втором устойстве нажмите кнопку профиля "поделиться", поднисите устройства друг к другу (тыльными сторонами).\nТак же Вы можете сканировать nfc метку.</string>
<string name="copy_url">Копировать URL</string>
<plurals name="removed">
<item quantity="one">Удалён</item>
<item quantity="other">%d элементы удалены</item>
</plurals>
<string name="undo">Вернуть</string>
<!-- tasker -->
<string name="toggle_service_state">Запустить сервисStart the service</string>
<string name="start_service_default">Подключиться к текущему серверу</string>
<string name="start_service">Подключиться к %s</string>
<string name="stop_service">Переключить на %s</string>
<string name="profile_default">Использовать текущий профиль</string>
<!-- status -->
<string name="sent">Отправлено:</string>
<string name="received">Получено:</string>
</resources>
+2 -1
View File
@@ -32,6 +32,7 @@
<string name="auto_connect_summary">随系统启动后台服务</string>
<string name="forward_success">后台服务已开始运行。</string>
<string name="service_failed">无法连接远程服务器</string>
<string name="invalid_server">服务器名无效</string>
<string name="nat_deprecated">警告:在 Android 5.0 及更高版本中不建议使用 NAT 模式</string>
<string name="nat_no_root">NAT 模式需要 ROOT 权限</string>
<string name="switch_to_vpn">切换到 VPN 模式</string>
@@ -72,7 +73,7 @@
<string name="udp_dns_summary">由远程服务器转发 UDP 协议的数据包</string>
<string name="onetime_auth">一次性认证</string>
<string name="onetime_auth_summary">启用一次性认证(实验性)</string>
<string name="onetime_auth_summary">启用一次性认证</string>
<string name="ipv6">IPv6 路由</string>
<string name="ipv6_summary">向远程服务器转发 IPv6 流量</string>
@@ -33,6 +33,7 @@
<string name="auto_connect_summary">在裝置啟動時啟用 Shadowsocks</string>
<string name="forward_success">Shadowsocks 已啟動。</string>
<string name="service_failed">連線至遠端伺服器失敗</string>
<string name="invalid_server">伺服器名稱無效</string>
<string name="nat_deprecated">警告:自 Android 5.0 開始, NAT 模式已過時</string>
<string name="nat_no_root">NAT 模式需要 ROOT 權限</string>
<string name="switch_to_vpn">切換至 VPN 模式</string>
@@ -73,7 +74,7 @@
<string name="udp_dns_summary">向遠端轉送 UDP 封包</string>
<string name="onetime_auth">單次驗證</string>
<string name="onetime_auth_summary">啟用單次驗證 (試驗性)</string>
<string name="onetime_auth_summary">啟用單次驗證</string>
<string name="ipv6">IPv6 路由</string>
<string name="ipv6_summary">向遠端重新導向 IPv6 流量</string>
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+2 -1
View File
@@ -43,7 +43,7 @@
<string name="ipv6">IPv6 Route</string>
<string name="ipv6_summary">Redirect IPv6 traffic to remote</string>
<string name="onetime_auth">One-time Authentication</string>
<string name="onetime_auth_summary">Enable one-time authentication (Experimental)</string>
<string name="onetime_auth_summary">Enable one-time authentication</string>
<string name="http_proxy">HTTP Proxy</string>
<string name="http_proxy_summary">Local HTTP Proxy</string>
<string name="dns_proxy">DNS Proxy</string>
@@ -64,6 +64,7 @@
<!-- notification category -->
<string name="forward_success">Shadowsocks started.</string>
<string name="invalid_server">Invalid server name</string>
<string name="service_failed">Failed to connect the remote server</string>
<string name="nat_deprecated">WARNING: NAT mode has been deprecated since Android 5.0</string>
<string name="nat_no_root">NAT mode requires ROOT permission</string>
+1
View File
@@ -70,6 +70,7 @@
<SwitchPreference
android:key="isAutoConnect"
android:persistent="false"
android:summary="@string/auto_connect_summary"
android:title="@string/auto_connect"/>
@@ -53,6 +53,7 @@ import android.support.v7.widget.{DefaultItemAnimator, LinearLayoutManager, Recy
import android.view._
import android.widget._
import com.github.shadowsocks.utils.{Key, Utils}
import com.github.shadowsocks.ShadowsocksApplication.app
import scala.collection.JavaConversions._
import scala.collection.mutable
@@ -71,7 +72,7 @@ object AppManager {
val filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED)
filter.addAction(Intent.ACTION_PACKAGE_REMOVED)
filter.addDataScheme("package")
ShadowsocksApplication.instance.registerReceiver((context: Context, intent: Intent) =>
app.registerReceiver((context: Context, intent: Intent) =>
if (intent.getAction != Intent.ACTION_PACKAGE_REMOVED ||
!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
synchronized(cachedApps = null)
@@ -96,13 +97,13 @@ class AppManager extends AppCompatActivity with OnMenuItemClickListener {
private final class AppViewHolder(val view: View) extends RecyclerView.ViewHolder(view) with View.OnClickListener {
private val icon = itemView.findViewById(R.id.itemicon).asInstanceOf[ImageView]
private val check = itemView.findViewById(R.id.itemcheck).asInstanceOf[Switch]
private var app: ProxiedApp = _
private var item: ProxiedApp = _
itemView.setOnClickListener(this)
private def proxied = proxiedApps.contains(app.packageName)
private def proxied = proxiedApps.contains(item.packageName)
def bind(app: ProxiedApp) {
this.app = app
this.item = app
icon.setImageDrawable(app.icon)
check.setText(app.name)
check.setChecked(proxied)
@@ -110,14 +111,14 @@ class AppManager extends AppCompatActivity with OnMenuItemClickListener {
def onClick(v: View) {
if (proxied) {
proxiedApps.remove(app.packageName)
proxiedApps.remove(item.packageName)
check.setChecked(false)
} else {
proxiedApps.add(app.packageName)
proxiedApps.add(item.packageName)
check.setChecked(true)
}
if (!appsLoading.get)
ShadowsocksApplication.settings.edit.putString(Key.proxied, proxiedApps.mkString("\n")).apply
app.editor.putString(Key.proxied, proxiedApps.mkString("\n")).apply
}
}
@@ -133,13 +134,16 @@ class AppManager extends AppCompatActivity with OnMenuItemClickListener {
new AppViewHolder(LayoutInflater.from(vg.getContext).inflate(R.layout.layout_apps_item, vg, false))
}
private val proxiedApps = ShadowsocksApplication.settings.getString(Key.proxied, "").split('\n').to[mutable.HashSet]
private var proxiedApps: mutable.HashSet[String] = _
private var toolbar: Toolbar = _
private var appListView: RecyclerView = _
private var loadingView: View = _
private val appsLoading = new AtomicBoolean
private var handler: Handler = null
private def initProxiedApps(str: String = app.settings.getString(Key.proxied, "")) =
proxiedApps = str.split('\n').to[mutable.HashSet]
override def onDestroy() {
instance = null
super.onDestroy()
@@ -169,14 +173,13 @@ class AppManager extends AppCompatActivity with OnMenuItemClickListener {
val editor = prefs.edit
val i = proxiedAppString.indexOf('\n')
try {
if (i < 0)
editor.putBoolean(Key.isBypassApps, proxiedAppString.toBoolean).putString(Key.proxied, "").apply()
else editor.putBoolean(Key.isBypassApps, proxiedAppString.substring(0, i).toBoolean)
.putString(Key.proxied, proxiedAppString.substring(i + 1)).apply()
val (enabled, apps) = if (i < 0) (proxiedAppString, "")
else (proxiedAppString.substring(0, i), proxiedAppString.substring(i + 1))
editor.putBoolean(Key.isBypassApps, enabled.toBoolean).putString(Key.proxied, apps).apply()
Toast.makeText(this, R.string.action_import_msg, Toast.LENGTH_SHORT).show()
// Restart activity
appListView.setVisibility(View.GONE)
loadingView.setVisibility(View.VISIBLE)
initProxiedApps(apps)
reloadApps()
return true
} catch {
@@ -208,18 +211,19 @@ class AppManager extends AppCompatActivity with OnMenuItemClickListener {
toolbar.inflateMenu(R.menu.app_manager_menu)
toolbar.setOnMenuItemClickListener(this)
ShadowsocksApplication.settings.edit().putBoolean(Key.isProxyApps, true).commit()
app.editor.putBoolean(Key.isProxyApps, true).apply
findViewById(R.id.onSwitch).asInstanceOf[Switch]
.setOnCheckedChangeListener((_, checked) => {
ShadowsocksApplication.settings.edit().putBoolean(Key.isProxyApps, checked).commit()
app.editor.putBoolean(Key.isProxyApps, checked).apply
finish()
})
val bypassSwitch = findViewById(R.id.bypassSwitch).asInstanceOf[Switch]
bypassSwitch.setOnCheckedChangeListener((_, checked) =>
ShadowsocksApplication.settings.edit().putBoolean(Key.isBypassApps, checked).apply())
bypassSwitch.setChecked(ShadowsocksApplication.settings.getBoolean(Key.isBypassApps, false))
app.editor.putBoolean(Key.isBypassApps, checked).apply())
bypassSwitch.setChecked(app.settings.getBoolean(Key.isBypassApps, false))
initProxiedApps()
loadingView = findViewById(R.id.loading)
appListView = findViewById(R.id.applistview).asInstanceOf[RecyclerView]
appListView.setLayoutManager(new LinearLayoutManager(this))
@@ -232,7 +236,7 @@ class AppManager extends AppCompatActivity with OnMenuItemClickListener {
def reloadApps() = if (!appsLoading.compareAndSet(true, false)) loadAppsAsync()
def loadAppsAsync() {
if (!appsLoading.compareAndSet(false, true)) return
ThrowableFuture {
Utils.ThrowableFuture {
var adapter: AppsAdapter = null
do {
appsLoading.set(true)
@@ -49,6 +49,7 @@ import android.util.Log
import android.widget.Toast
import com.github.shadowsocks.aidl.{Config, IShadowsocksService, IShadowsocksServiceCallback}
import com.github.shadowsocks.utils._
import com.github.shadowsocks.ShadowsocksApplication.app
trait BaseService extends Service {
@@ -60,7 +61,7 @@ trait BaseService extends Service {
final val callbacks = new RemoteCallbackList[IShadowsocksServiceCallback]
var callbacksCount: Int = _
lazy val handler = new Handler(getContext.getMainLooper)
lazy val handler = new Handler(getMainLooper)
private val closeReceiver: BroadcastReceiver = (context: Context, intent: Intent) => {
Toast.makeText(context, R.string.stopping, Toast.LENGTH_SHORT).show()
@@ -69,10 +70,6 @@ trait BaseService extends Service {
var closeReceiverRegistered: Boolean = _
val binder = new IShadowsocksService.Stub {
override def getMode: Int = {
getServiceMode
}
override def getState: Int = {
state
}
@@ -125,7 +122,7 @@ trait BaseService extends Service {
def startRunner(config: Config) {
this.config = config
startService(new Intent(getContext, getClass))
startService(new Intent(this, getClass))
TrafficMonitor.reset()
trafficMonitorThread = new TrafficMonitorThread(getApplicationContext)
trafficMonitorThread.start()
@@ -166,26 +163,19 @@ trait BaseService extends Service {
def updateTrafficTotal(tx: Long, rx: Long) {
val config = this.config // avoid race conditions without locking
if (config != null) {
ShadowsocksApplication.profileManager.getProfile(config.profileId) match {
app.profileManager.getProfile(config.profileId) match {
case Some(profile) =>
profile.tx += tx
profile.rx += rx
ShadowsocksApplication.profileManager.updateProfile(profile)
app.profileManager.updateProfile(profile)
case None => // Ignore
}
}
}
def getServiceMode: Int
def getTag: String
def getContext: Context
def getState: Int = {
state
}
def changeState(s: Int) {
changeState(s, null)
}
def updateTrafficRate() {
handler.post(() => {
@@ -210,8 +200,8 @@ trait BaseService extends Service {
// Service of shadowsocks should always be started explicitly
override def onStartCommand(intent: Intent, flags: Int, startId: Int): Int = Service.START_NOT_STICKY
protected def changeState(s: Int, msg: String) {
val handler = new Handler(getContext.getMainLooper)
protected def changeState(s: Int, msg: String = null) {
val handler = new Handler(getMainLooper)
handler.post(() => if (state != s) {
if (callbacksCount > 0) {
val n = callbacks.beginBroadcast()
@@ -39,13 +39,19 @@
package com.github.shadowsocks
import android.content.{BroadcastReceiver, Context, Intent}
import android.content.pm.PackageManager
import android.content.{BroadcastReceiver, ComponentName, Context, Intent}
import com.github.shadowsocks.utils._
class ShadowsocksReceiver extends BroadcastReceiver {
def onReceive(context: Context, intent: Intent) {
if (ShadowsocksApplication.settings.getBoolean(Key.isAutoConnect, false)) {
Utils.startSsService(context)
}
}
object BootReceiver {
def getEnabled(context: Context) = PackageManager.COMPONENT_ENABLED_STATE_ENABLED ==
context.getPackageManager.getComponentEnabledSetting(new ComponentName(context, classOf[BootReceiver]))
def setEnabled(context: Context, enabled: Boolean) = context.getPackageManager.setComponentEnabledSetting(
new ComponentName(context, classOf[BootReceiver]),
if (enabled) PackageManager.COMPONENT_ENABLED_STATE_ENABLED else PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP)
}
class BootReceiver extends BroadcastReceiver {
def onReceive(context: Context, intent: Intent) = Utils.startSsService(context)
}
@@ -22,6 +22,7 @@ import com.github.shadowsocks.aidl.IShadowsocksServiceCallback
import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.utils.{Key, Parser, TrafficMonitor, Utils}
import com.github.shadowsocks.widget.UndoSnackbarManager
import com.github.shadowsocks.ShadowsocksApplication.app
import com.google.zxing.integration.android.IntentIntegrator
import net.glxn.qrgen.android.QRCode
@@ -96,7 +97,7 @@ final class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClic
def bind(item: Profile) {
this.item = item
updateText()
if (item.id == ShadowsocksApplication.profileId) {
if (item.id == app.profileId) {
text.setChecked(true)
selectedItem = this
} else {
@@ -106,7 +107,7 @@ final class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClic
}
def onClick(v: View) {
ShadowsocksApplication.switchProfile(item.id)
app.switchProfile(item.id)
finish
}
@@ -122,7 +123,7 @@ final class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClic
private class ProfilesAdapter extends RecyclerView.Adapter[ProfileViewHolder] {
var profiles = new ArrayBuffer[Profile]
profiles ++= ShadowsocksApplication.profileManager.getAllProfiles.getOrElse(List.empty[Profile])
profiles ++= app.profileManager.getAllProfiles.getOrElse(List.empty[Profile])
def getItemCount = profiles.length
@@ -149,11 +150,11 @@ final class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClic
next.userOrder = previousOrder
previousOrder = order
profiles(i) = next
ShadowsocksApplication.profileManager.updateProfile(next)
app.profileManager.updateProfile(next)
}
first.userOrder = previousOrder
profiles(to) = first
ShadowsocksApplication.profileManager.updateProfile(first)
app.profileManager.updateProfile(first)
notifyItemMoved(from, to)
}
@@ -166,8 +167,8 @@ final class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClic
notifyItemInserted(index)
}
def commit(actions: Iterator[(Int, Profile)]) = for ((index, item) <- actions) {
ShadowsocksApplication.profileManager.delProfile(item.id)
if (item.id == ShadowsocksApplication.profileId) ShadowsocksApplication.profileId(-1)
app.profileManager.delProfile(item.id)
if (item.id == app.profileId) app.profileId(-1)
}
}
@@ -193,7 +194,7 @@ final class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClic
val toolbar = findViewById(R.id.toolbar).asInstanceOf[Toolbar]
toolbar.setTitle(R.string.profiles)
toolbar.setNavigationIcon(R.drawable.abc_ic_ab_back_mtrl_am_alpha)
toolbar.setNavigationIcon(R.drawable.ic_navigation_close)
toolbar.setNavigationOnClickListener((v: View) => {
val intent = getParentActivityIntent
if (intent == null) finish else navigateUpTo(intent)
@@ -203,14 +204,14 @@ final class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClic
initFab()
ShadowsocksApplication.profileManager.setProfileAddedListener(profilesAdapter.add)
app.profileManager.setProfileAddedListener(profilesAdapter.add)
val profilesList = findViewById(R.id.profilesList).asInstanceOf[RecyclerView]
val layoutManager = new LinearLayoutManager(this)
profilesList.setLayoutManager(layoutManager)
profilesList.setItemAnimator(new DefaultItemAnimator)
profilesList.setAdapter(profilesAdapter)
layoutManager.scrollToPosition(profilesAdapter.profiles.zipWithIndex.collectFirst {
case (profile, i) if profile.id == ShadowsocksApplication.profileId => i
case (profile, i) if profile.id == app.profileId => i
}.getOrElse(-1))
undoManager = new UndoSnackbarManager[Profile](profilesList, profilesAdapter.undo, profilesAdapter.commit)
new ItemTouchHelper(new SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN,
@@ -232,8 +233,8 @@ final class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClic
if (selectedItem != null) selectedItem.updateText(txTotal, rxTotal)
})
if (ShadowsocksApplication.settings.getBoolean(Key.profileTip, true)) {
ShadowsocksApplication.settings.edit.putBoolean(Key.profileTip, false).commit
if (app.settings.getBoolean(Key.profileTip, true)) {
app.editor.putBoolean(Key.profileTip, false).apply
new AlertDialog.Builder(this).setTitle(R.string.profile_manager_dialog)
.setMessage(R.string.profile_manager_dialog_content).setPositiveButton(R.string.gotcha, null).create.show
}
@@ -270,8 +271,8 @@ final class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClic
v.getId match {
case R.id.fab_manual_add =>
menu.toggle(true)
ShadowsocksApplication.profileManager.reload(-1)
ShadowsocksApplication.switchProfile(ShadowsocksApplication.profileManager.save.id)
app.profileManager.reload(-1)
app.switchProfile(app.profileManager.save.id)
finish
case R.id.fab_qrcode_add =>
menu.toggle(false)
@@ -301,7 +302,7 @@ final class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClic
if (clipboard.hasPrimaryClip) {
val profiles = Parser.findAll(clipboard.getPrimaryClip.getItemAt(0).getText)
if (profiles.nonEmpty) {
profiles.foreach(ShadowsocksApplication.profileManager.createProfile)
profiles.foreach(app.profileManager.createProfile)
Toast.makeText(this, R.string.action_import_msg, Toast.LENGTH_SHORT).show
return
}
@@ -346,7 +347,7 @@ final class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClic
val dialog = new AlertDialog.Builder(this)
.setTitle(R.string.add_profile_dialog)
.setPositiveButton(android.R.string.yes, ((_, _) =>
profiles.foreach(ShadowsocksApplication.profileManager.createProfile)): DialogInterface.OnClickListener)
profiles.foreach(app.profileManager.createProfile)): DialogInterface.OnClickListener)
.setNegativeButton(android.R.string.no, ((_, _) => finish()): DialogInterface.OnClickListener)
.setMessage(profiles.mkString("\n"))
.create()
@@ -363,9 +364,9 @@ final class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClic
}
override def onDestroy {
deattachService()
detachService()
undoManager.flush
ShadowsocksApplication.profileManager.setProfileAddedListener(null)
app.profileManager.setProfileAddedListener(null)
super.onDestroy
}
@@ -374,7 +375,7 @@ final class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClic
if (scanResult != null) {
val contents = scanResult.getContents
if (!TextUtils.isEmpty(contents))
Parser.findAll(contents).foreach(ShadowsocksApplication.profileManager.createProfile)
Parser.findAll(contents).foreach(app.profileManager.createProfile)
}
}
@@ -387,7 +388,7 @@ final class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClic
def onMenuItemClick(item: MenuItem): Boolean = item.getItemId match {
case R.id.action_export =>
ShadowsocksApplication.profileManager.getAllProfiles match {
app.profileManager.getAllProfiles match {
case Some(profiles) =>
clipboard.setPrimaryClip(ClipData.newPlainText(null, profiles.mkString("\n")))
Toast.makeText(this, R.string.action_export_msg, Toast.LENGTH_SHORT).show
@@ -4,25 +4,25 @@ import android.content.{ComponentName, Context, Intent, ServiceConnection}
import android.os.{RemoteException, IBinder}
import com.github.shadowsocks.aidl.{IShadowsocksServiceCallback, IShadowsocksService}
import com.github.shadowsocks.utils.Action
import com.github.shadowsocks.ShadowsocksApplication.app
/**
* @author Mygod
*/
trait ServiceBoundContext extends Context {
trait ServiceBoundContext extends Context with IBinder.DeathRecipient {
class ShadowsocksServiceConnection extends ServiceConnection {
override def onServiceConnected(name: ComponentName, service: IBinder) {
service.linkToDeath(new ShadowsocksDeathRecipient(ServiceBoundContext.this), 0)
binder = service
service.linkToDeath(ServiceBoundContext.this, 0)
bgService = IShadowsocksService.Stub.asInterface(service)
registerCallback
ServiceBoundContext.this.onServiceConnected()
}
override def onServiceDisconnected(name: ComponentName) {
if (callback != null) {
unregisterCallback
callback = null
}
unregisterCallback
ServiceBoundContext.this.onServiceDisconnected()
bgService = null
binder = null
}
}
@@ -42,19 +42,20 @@ trait ServiceBoundContext extends Context {
def onServiceConnected() = ()
def onServiceDisconnected() = ()
override def binderDied = ()
private var callback: IShadowsocksServiceCallback.Stub = _
private var connection: ShadowsocksServiceConnection = _
private var callbackRegistered: Boolean = _
// Variables
var binder: IBinder = _
var bgService: IShadowsocksService = _
def attachService(callback: IShadowsocksServiceCallback.Stub = null) {
this.callback = callback
if (bgService == null) {
val s =
if (ShadowsocksApplication.isVpnEnabled) classOf[ShadowsocksVpnService] else classOf[ShadowsocksNatService]
val s = if (app.isNatEnabled) classOf[ShadowsocksNatService] else classOf[ShadowsocksVpnService]
val intent = new Intent(this, s)
intent.setAction(Action.SERVICE)
@@ -65,13 +66,17 @@ trait ServiceBoundContext extends Context {
}
}
def deattachService() {
def detachService() {
unregisterCallback
callback = null
if (connection != null) {
unbindService(connection)
connection = null
}
if (binder != null) {
binder.unlinkToDeath(this, 0)
binder = null
}
bgService = null
}
}
@@ -56,13 +56,14 @@ import android.support.v4.content.ContextCompat
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.Toolbar
import android.util.Log
import android.view.{View, ViewGroup, ViewParent}
import android.view.{View, ViewGroup}
import android.widget._
import com.github.jorgecastilloprz.FABProgressCircle
import com.github.shadowsocks.aidl.IShadowsocksServiceCallback
import com.github.shadowsocks.database._
import com.github.shadowsocks.utils.CloseUtils._
import com.github.shadowsocks.utils._
import com.github.shadowsocks.ShadowsocksApplication.app
import com.google.android.gms.ads.{AdRequest, AdSize, AdView}
import scala.collection.mutable.ArrayBuffer
@@ -88,29 +89,25 @@ object Typefaces {
}
object Shadowsocks {
// Constants
val TAG = "Shadowsocks"
val REQUEST_CONNECT = 1
val EXECUTABLES = Array(Executable.PDNSD, Executable.REDSOCKS, Executable.SS_TUNNEL, Executable.SS_LOCAL,
private final val TAG = "Shadowsocks"
private final val REQUEST_CONNECT = 1
private val EXECUTABLES = Array(Executable.PDNSD, Executable.REDSOCKS, Executable.SS_TUNNEL, Executable.SS_LOCAL,
Executable.TUN2SOCKS)
}
class Shadowsocks
extends AppCompatActivity with ServiceBoundContext {
class Shadowsocks extends AppCompatActivity with ServiceBoundContext {
import Shadowsocks._
// Variables
var serviceStarted = false
var fab: FloatingActionButton = _
var fabProgressCircle: FABProgressCircle = _
var progressDialog: ProgressDialog = _
var progressTag = -1
var state = State.STOPPED
var currentProfile = new Profile
// Services
var currentServiceName = classOf[ShadowsocksNatService].getName
private val callback = new IShadowsocksServiceCallback.Stub {
def stateChanged(s: Int, m: String) {
handler.post(() => if (state != s) {
@@ -133,10 +130,10 @@ class Shadowsocks
changeSwitch(checked = true)
preferences.setEnabled(false)
stat.setVisibility(View.VISIBLE)
if (ShadowsocksApplication.isVpnEnabled) {
if (app.isNatEnabled) connectionTestText.setVisibility(View.GONE) else {
connectionTestText.setVisibility(View.VISIBLE)
connectionTestText.setText(getString(R.string.connection_test_pending))
} else connectionTestText.setVisibility(View.GONE)
}
case State.STOPPED =>
fab.setBackgroundTintList(greyTint)
fabProgressCircle.postDelayed(hideCircle, 1000)
@@ -148,7 +145,7 @@ class Shadowsocks
if (m == getString(R.string.nat_no_root)) snackbar.setAction(R.string.switch_to_vpn,
(_ => preferences.natSwitch.setChecked(false)): View.OnClickListener)
snackbar.show
Log.e(Shadowsocks.TAG, "Error to start VPN service: " + m)
Log.e(TAG, "Error to start VPN service: " + m)
}
preferences.setEnabled(true)
stat.setVisibility(View.GONE)
@@ -180,34 +177,36 @@ class Shadowsocks
override def onServiceConnected() {
// Update the UI
if (fab != null) fab.setEnabled(true)
updateState()
if (Build.VERSION.SDK_INT >= 21 && !ShadowsocksApplication.isVpnEnabled) {
updateState(true)
if (Build.VERSION.SDK_INT >= 21 && app.isNatEnabled) {
val snackbar = Snackbar.make(findViewById(android.R.id.content), R.string.nat_deprecated, Snackbar.LENGTH_LONG)
snackbar.setAction(R.string.switch_to_vpn, (_ => preferences.natSwitch.setChecked(false)): View.OnClickListener)
snackbar.show
}
if (!ShadowsocksApplication.settings.getBoolean(ShadowsocksApplication.getVersionName, false)) {
ShadowsocksApplication.settings.edit.putBoolean(ShadowsocksApplication.getVersionName, true).apply()
if (!app.settings.getBoolean(app.getVersionName, false)) {
app.editor.putBoolean(app.getVersionName, true).apply()
try {
// Workaround that convert port(String) to port(Int)
val oldLocalPort = ShadowsocksApplication.settings.getString(Key.localPort, "")
val oldRemotePort = ShadowsocksApplication.settings.getString(Key.remotePort, "")
val oldLocalPort = app.settings.getString(Key.localPort, "")
val oldRemotePort = app.settings.getString(Key.remotePort, "")
if (oldLocalPort != "") {
ShadowsocksApplication.settings.edit.putInt(Key.localPort, oldLocalPort.toInt).apply()
app.editor.putInt(Key.localPort, oldLocalPort.toInt).apply()
}
if (oldRemotePort != "") {
ShadowsocksApplication.settings.edit.putInt(Key.remotePort, oldRemotePort.toInt).apply()
app.editor.putInt(Key.remotePort, oldRemotePort.toInt).apply()
}
} catch {
case ex: Exception => // Ignore
}
val oldProxiedApps = ShadowsocksApplication.settings.getString(Key.proxied, "")
if (oldProxiedApps.contains('|')) ShadowsocksApplication.settings.edit
.putString(Key.proxied, DBHelper.updateProxiedApps(this, oldProxiedApps)).apply()
val oldProxiedApps = app.settings.getString(Key.proxied, "")
if (oldProxiedApps.contains('|'))
app.editor.putString(Key.proxied, DBHelper.updateProxiedApps(this, oldProxiedApps)).apply()
recovery()
updateCurrentProfile()
}
}
@@ -215,6 +214,12 @@ class Shadowsocks
if (fab != null) fab.setEnabled(false)
}
override def binderDied {
detachService
crashRecovery
attachService
}
private var testCount: Int = _
private lazy val stat = findViewById(R.id.stat)
private var connectionTestText: TextView = _
@@ -228,7 +233,7 @@ class Shadowsocks
private lazy val preferences =
getFragmentManager.findFragmentById(android.R.id.content).asInstanceOf[ShadowsocksSettings]
var handler = new Handler()
val handler = new Handler()
private def changeSwitch(checked: Boolean) {
serviceStarted = checked
@@ -242,7 +247,6 @@ class Shadowsocks
private def showProgress(msg: Int): Handler = {
clearDialog()
progressDialog = ProgressDialog.show(this, "", getString(msg), true, false)
progressTag = msg
new Handler {
override def handleMessage(msg: Message) {
clearDialog()
@@ -257,7 +261,7 @@ class Shadowsocks
files = assetManager.list(path)
} catch {
case e: IOException =>
Log.e(Shadowsocks.TAG, e.getMessage)
Log.e(TAG, e.getMessage)
}
if (files != null) {
for (file <- files) {
@@ -278,7 +282,7 @@ class Shadowsocks
out = null
} catch {
case ex: Exception =>
Log.e(Shadowsocks.TAG, ex.getMessage)
Log.e(TAG, ex.getMessage)
}
}
}
@@ -303,11 +307,7 @@ class Shadowsocks
cmd.append("chmod 666 %s/%s-vpn.pid".formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir, task))
}
if (ShadowsocksApplication.isVpnEnabled) {
Console.runCommand(cmd.toArray)
} else {
Console.runRootCommand(cmd.toArray)
}
if (app.isNatEnabled) Console.runRootCommand(cmd.toArray) else Console.runCommand(cmd.toArray)
cmd.clear()
@@ -327,10 +327,10 @@ class Shadowsocks
cmd.append("rm -f %s/%s-vpn.pid".formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir, task))
cmd.append("rm -f %s/%s-vpn.conf".formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir, task))
}
if (ShadowsocksApplication.isVpnEnabled) Console.runCommand(cmd.toArray) else {
if (app.isNatEnabled) {
Console.runRootCommand(cmd.toArray)
Console.runRootCommand(Utils.getIptables + " -t nat -F OUTPUT")
}
Console.runRootCommand(Utils.iptables + " -t nat -F OUTPUT")
} else Console.runCommand(cmd.toArray)
}
def cancelStart() {
@@ -339,27 +339,18 @@ class Shadowsocks
}
def prepareStartService() {
ThrowableFuture {
if (ShadowsocksApplication.isVpnEnabled) {
Utils.ThrowableFuture {
if (app.isNatEnabled) serviceLoad() else {
val intent = VpnService.prepare(this)
if (intent != null) {
startActivityForResult(intent, Shadowsocks.REQUEST_CONNECT)
startActivityForResult(intent, REQUEST_CONNECT)
} else {
handler.post(() => onActivityResult(Shadowsocks.REQUEST_CONNECT, Activity.RESULT_OK, null))
handler.post(() => onActivityResult(REQUEST_CONNECT, Activity.RESULT_OK, null))
}
} else {
serviceLoad()
}
}
}
def getLayoutView(view: ViewParent): LinearLayout = {
view match {
case layout: LinearLayout => layout
case _ => if (view != null) getLayoutView(view.getParent) else null
}
}
override def onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
@@ -391,7 +382,7 @@ class Shadowsocks
handler.post(() => connectionTestText.setText(R.string.connection_test_testing))
testCount
}
ThrowableFuture {
Utils.ThrowableFuture {
// Based on: https://android.googlesource.com/platform/frameworks/base/+/master/services/core/java/com/android/server/connectivity/NetworkMonitor.java#640
autoDisconnect(new URL("https", "www.google.com", "/generate_204").openConnection()
.asInstanceOf[HttpURLConnection]) { conn =>
@@ -415,14 +406,11 @@ class Shadowsocks
success = false
result = getString(R.string.connection_test_error, e.getMessage)
}
synchronized(if (testCount == id) {
if (ShadowsocksApplication.isVpnEnabled) handler.post(() => {
if (success) connectionTestText.setText(result) else {
connectionTestText.setText(R.string.connection_test_fail)
Snackbar.make(findViewById(android.R.id.content), result, Snackbar.LENGTH_LONG).show
}
})
})
synchronized(if (testCount == id && app.isVpnEnabled) handler.post(() =>
if (success) connectionTestText.setText(result) else {
connectionTestText.setText(R.string.connection_test_fail)
Snackbar.make(findViewById(android.R.id.content), result, Snackbar.LENGTH_LONG).show
}))
}
}
}
@@ -445,7 +433,7 @@ class Shadowsocks
protected override def onPause() {
super.onPause()
ShadowsocksApplication.profileManager.save
app.profileManager.save
}
private def hideCircle() {
@@ -456,7 +444,7 @@ class Shadowsocks
}
}
private def updateState() {
private def updateState(initial: Boolean = false) {
if (bgService != null) {
bgService.getState match {
case State.CONNECTING =>
@@ -473,10 +461,10 @@ class Shadowsocks
preferences.setEnabled(false)
fabProgressCircle.postDelayed(hideCircle, 100)
stat.setVisibility(View.VISIBLE)
if (ShadowsocksApplication.isVpnEnabled) {
if (initial) if (app.isNatEnabled) connectionTestText.setVisibility(View.GONE) else {
connectionTestText.setVisibility(View.VISIBLE)
connectionTestText.setText(getString(R.string.connection_test_pending))
} else connectionTestText.setVisibility(View.GONE)
}
case State.STOPPING =>
fab.setBackgroundTintList(greyTint)
serviceStarted = false
@@ -496,18 +484,15 @@ class Shadowsocks
}
}
protected override def onResume() {
super.onResume()
ConfigUtils.refresh(this)
private def updateCurrentProfile() {
// Check if current profile changed
if (ShadowsocksApplication.profileId != currentProfile.id) {
currentProfile = ShadowsocksApplication.currentProfile match {
if (app.profileId != currentProfile.id) {
currentProfile = app.currentProfile match {
case Some(profile) => profile // updated
case None => // removed
ShadowsocksApplication.profileManager.getFirstProfile match {
case Some(first) => ShadowsocksApplication.switchProfile(first.id)
case None => ShadowsocksApplication.profileManager.createDefault()
app.profileManager.getFirstProfile match {
case Some(first) => app.switchProfile(first.id)
case None => app.profileManager.createDefault()
}
}
@@ -515,6 +500,14 @@ class Shadowsocks
if (serviceStarted) serviceLoad()
}
}
protected override def onResume() {
super.onResume()
ConfigUtils.refresh(this)
updateCurrentProfile()
updateState()
}
@@ -547,7 +540,7 @@ class Shadowsocks
override def onDestroy() {
super.onDestroy()
_isDestroyed = true
deattachService()
detachService()
new BackupManager(this).dataChanged()
handler.removeCallbacksAndMessages(null)
}
@@ -558,7 +551,7 @@ class Shadowsocks
copyAssets(System.getABI)
val ab = new ArrayBuffer[String]
for (executable <- Shadowsocks.EXECUTABLES) {
for (executable <- EXECUTABLES) {
ab.append("chmod 755 " + getApplicationInfo.dataDir + "/" + executable)
}
Console.runCommand(ab.toArray)
@@ -567,7 +560,7 @@ class Shadowsocks
def recovery() {
if (serviceStarted) serviceStop()
val h = showProgress(R.string.recovering)
ThrowableFuture {
Utils.ThrowableFuture {
reset()
h.sendEmptyMessage(0)
}
@@ -575,7 +568,7 @@ class Shadowsocks
def flushDnsCache() {
val h = showProgress(R.string.flushing)
ThrowableFuture {
Utils.ThrowableFuture {
if (!Utils.toggleAirplaneMode(getBaseContext)) h.post(() => Snackbar.make(findViewById(android.R.id.content),
R.string.flush_dnscache_no_root, Snackbar.LENGTH_LONG).show)
h.sendEmptyMessage(0)
@@ -587,7 +580,7 @@ class Shadowsocks
serviceLoad()
case _ =>
cancelStart()
Log.e(Shadowsocks.TAG, "Failed to start VpnService")
Log.e(TAG, "Failed to start VpnService")
}
def serviceStop() {
@@ -595,7 +588,7 @@ class Shadowsocks
}
def checkText(key: String): Boolean = {
val text = ShadowsocksApplication.settings.getString(key, "")
val text = app.settings.getString(key, "")
if (text != null && text.length > 0) return true
Snackbar.make(findViewById(android.R.id.content), R.string.proxy_empty, Snackbar.LENGTH_LONG).show
false
@@ -603,9 +596,9 @@ class Shadowsocks
/** Called when connect button is clicked. */
def serviceLoad() {
bgService.use(ConfigUtils.load(ShadowsocksApplication.settings))
bgService.use(ConfigUtils.loadFromSharedPreferences)
if (ShadowsocksApplication.isVpnEnabled) {
if (app.isVpnEnabled) {
changeSwitch(checked = false)
}
}
@@ -614,7 +607,6 @@ class Shadowsocks
if (progressDialog != null && progressDialog.isShowing) {
if (!isDestroyed) progressDialog.dismiss()
progressDialog = null
progressTag = -1
}
}
}
@@ -45,26 +45,33 @@ import java.util.concurrent.TimeUnit
import android.app.Application
import android.content.pm.PackageManager
import android.preference.PreferenceManager
import com.j256.ormlite.logger.LocalLog
import com.github.shadowsocks.database.{DBHelper, ProfileManager}
import com.github.shadowsocks.utils.{Key, Utils}
import com.google.android.gms.analytics.{GoogleAnalytics, HitBuilders}
import com.google.android.gms.common.api.ResultCallback
import com.google.android.gms.tagmanager.{ContainerHolder, TagManager}
import com.j256.ormlite.logger.LocalLog
object ShadowsocksApplication {
var instance: ShadowsocksApplication = _
lazy val dbHelper = new DBHelper(instance)
var app: ShadowsocksApplication = _
}
class ShadowsocksApplication extends Application {
import ShadowsocksApplication._
lazy val dbHelper = new DBHelper(this)
final val SIG_FUNC = "getSignature"
var containerHolder: ContainerHolder = _
lazy val tracker = GoogleAnalytics.getInstance(instance).newTracker(R.xml.tracker)
lazy val settings = PreferenceManager.getDefaultSharedPreferences(instance)
lazy val tracker = GoogleAnalytics.getInstance(this).newTracker(R.xml.tracker)
lazy val settings = PreferenceManager.getDefaultSharedPreferences(this)
lazy val editor = settings.edit
lazy val profileManager = new ProfileManager(settings, dbHelper)
def isVpnEnabled = !settings.getBoolean(Key.isNAT, false)
def isNatEnabled = settings.getBoolean(Key.isNAT, false)
def isVpnEnabled = !isNatEnabled
def getVersionName = try {
instance.getPackageManager.getPackageInfo(instance.getPackageName, 0).versionName
getPackageManager.getPackageInfo(getPackageName, 0).versionName
} catch {
case _: PackageManager.NameNotFoundException => "Package name not found"
case _: Throwable => null
@@ -84,14 +91,10 @@ object ShadowsocksApplication {
profileId(id)
profileManager.load(id)
}
}
class ShadowsocksApplication extends Application {
import ShadowsocksApplication._
override def onCreate() {
java.lang.System.setProperty(LocalLog.LOCAL_LOG_LEVEL_PROPERTY, "ERROR");
ShadowsocksApplication.instance = this
java.lang.System.setProperty(LocalLog.LOCAL_LOG_LEVEL_PROPERTY, "ERROR")
app = this
val tm = TagManager.getInstance(this)
val pending = tm.loadContainerPreferNonDefault("GTM-NT8WS8", R.raw.gtm_default_container)
val callback = new ResultCallback[ContainerHolder] {
@@ -1,24 +0,0 @@
package com.github.shadowsocks
import android.os.IBinder
import android.util.Log
/**
* @author chentaov5@gmail.com
*/
class ShadowsocksDeathRecipient(val mContext: ServiceBoundContext)
extends IBinder.DeathRecipient {
val TAG = "ShadowsocksDeathRecipient"
override def binderDied(): Unit = {
Log.d(TAG, "[ShadowsocksDeathRecipient] binder died.")
mContext match {
case ss: Shadowsocks =>
ss.deattachService
ss.crashRecovery
ss.attachService
case _ =>
}
}
}
@@ -45,12 +45,12 @@ import java.net.{Inet6Address, InetAddress}
import java.util.Locale
import android.content._
import android.content.pm.{PackageInfo, PackageManager}
import android.net.{ConnectivityManager, Network}
import android.os._
import android.util.{Log, SparseArray}
import com.github.shadowsocks.aidl.Config
import com.github.shadowsocks.utils._
import com.github.shadowsocks.ShadowsocksApplication.app
import scala.collection.JavaConversions._
import scala.collection.mutable.ArrayBuffer
@@ -64,7 +64,6 @@ class ShadowsocksNatService extends BaseService {
"-j DNAT --to-destination 127.0.0.1:8123"
private var notification: ShadowsocksNotification = _
var connReceiver: BroadcastReceiver = _
val myUid = android.os.Process.myUid()
var sslocalProcess: Process = _
@@ -144,21 +143,6 @@ class ShadowsocksNatService extends BaseService {
}
}
def destroyConnectionReceiver() {
if (connReceiver != null) {
unregisterReceiver(connReceiver)
connReceiver = null
}
resetDns()
}
def initConnectionReceiver() {
val filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)
connReceiver = (context: Context, intent: Intent) => setupDns()
registerReceiver(connReceiver, filter)
}
def startShadowsocksDaemon() {
if (config.route != Route.ALL) {
val acl: Array[Array[String]] = config.route match {
@@ -250,8 +234,8 @@ class ShadowsocksNatService extends BaseService {
def startDnsDaemon() {
val conf = if (config.route == Route.BYPASS_CHN || config.route == Route.BYPASS_LAN_CHN) {
val reject = ConfigUtils.getRejectList(getContext)
val blackList = ConfigUtils.getBlackList(getContext)
val reject = ConfigUtils.getRejectList(this)
val blackList = ConfigUtils.getBlackList(this)
ConfigUtils.PDNSD_DIRECT.formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir,
"127.0.0.1", 8153, reject, blackList, 8163, "")
} else {
@@ -269,18 +253,6 @@ class ShadowsocksNatService extends BaseService {
pdnsdProcess = new GuardedProcess(cmd.split(" ").toSeq).start()
}
def getVersionName: String = {
var version: String = null
try {
val pi: PackageInfo = getPackageManager.getPackageInfo(getPackageName, 0)
version = pi.versionName
} catch {
case e: PackageManager.NameNotFoundException =>
version = "Package name not found"
}
version
}
def startRedsocksDaemon() {
val conf = ConfigUtils.REDSOCKS.formatLocal(Locale.ENGLISH, config.localPort)
val cmd = "%s/redsocks -c %s/redsocks-nat.conf"
@@ -337,7 +309,7 @@ class ShadowsocksNatService extends BaseService {
pdnsdProcess = null
}
Console.runRootCommand(Utils.getIptables + " -t nat -F OUTPUT")
Console.runRootCommand(Utils.iptables + " -t nat -F OUTPUT")
}
def setupIptables() = {
@@ -345,9 +317,9 @@ class ShadowsocksNatService extends BaseService {
val http_sb = new ArrayBuffer[String]
init_sb.append("ulimit -n 4096")
init_sb.append(Utils.getIptables + " -t nat -F OUTPUT")
init_sb.append(Utils.iptables + " -t nat -F OUTPUT")
val cmd_bypass = Utils.getIptables + CMD_IPTABLES_RETURN
val cmd_bypass = Utils.iptables + CMD_IPTABLES_RETURN
if (!InetAddress.getByName(config.proxy.toUpperCase).isInstanceOf[Inet6Address]) {
init_sb.append(cmd_bypass.replace("-p tcp -d 0.0.0.0", "-d " + config.proxy))
}
@@ -355,18 +327,18 @@ class ShadowsocksNatService extends BaseService {
init_sb.append(cmd_bypass.replace("-p tcp -d 0.0.0.0", "-m owner --uid-owner " + myUid))
init_sb.append(cmd_bypass.replace("-d 0.0.0.0", "--dport 53"))
init_sb.append(Utils.getIptables
init_sb.append(Utils.iptables
+ " -t nat -A OUTPUT -p udp --dport 53 -j DNAT --to-destination 127.0.0.1:8153")
if (!config.isProxyApps || config.isBypassApps) {
http_sb.append(Utils.getIptables + CMD_IPTABLES_DNAT_ADD_SOCKS)
http_sb.append(Utils.iptables + CMD_IPTABLES_DNAT_ADD_SOCKS)
}
if (config.isProxyApps) {
val uidMap = getPackageManager.getInstalledApplications(0).map(ai => ai.packageName -> ai.uid).toMap
for (pn <- config.proxiedAppString.split('\n')) uidMap.get(pn) match {
case Some(uid) =>
if (!config.isBypassApps) {
http_sb.append((Utils.getIptables + CMD_IPTABLES_DNAT_ADD_SOCKS)
http_sb.append((Utils.iptables + CMD_IPTABLES_DNAT_ADD_SOCKS)
.replace("-t nat", "-t nat -m owner --uid-owner " + uid))
} else {
init_sb.append(cmd_bypass.replace("-d 0.0.0.0", "-m owner --uid-owner " + uid))
@@ -384,13 +356,13 @@ class ShadowsocksNatService extends BaseService {
}
super.startRunner(config)
ShadowsocksApplication.track(TAG, "start")
app.track(TAG, "start")
changeState(State.CONNECTING)
ThrowableFuture {
Utils.ThrowableFuture {
if (config.proxy == "198.199.101.152") {
val holder = ShadowsocksApplication.containerHolder
val holder = app.containerHolder
try {
this.config = ConfigUtils.getPublicConfig(getBaseContext, holder.getContainer, config)
} catch {
@@ -418,11 +390,12 @@ class ShadowsocksNatService extends BaseService {
resolved = true
}
if (resolved && handleConnection) {
if (!resolved) {
changeState(State.STOPPED, getString(R.string.invalid_server))
stopRunner(true)
} else if (handleConnection) {
// Set DNS
flushDns()
changeState(State.CONNECTED)
notification = new ShadowsocksNotification(this, config.profileName, true)
} else {
@@ -440,15 +413,11 @@ class ShadowsocksNatService extends BaseService {
if (notification != null) notification.destroy()
ShadowsocksApplication.track(TAG, "stop")
app.track(TAG, "stop")
// reset NAT
killProcesses()
super.stopRunner(stopService)
}
override def getTag = TAG
override def getServiceMode = Mode.NAT
override def getContext = getBaseContext
}
@@ -10,6 +10,7 @@ import android.support.v4.app.NotificationCompat.BigTextStyle
import android.support.v4.content.ContextCompat
import com.github.shadowsocks.aidl.IShadowsocksServiceCallback.Stub
import com.github.shadowsocks.utils.{TrafficMonitor, Action, State, Utils}
import com.github.shadowsocks.ShadowsocksApplication.app
/**
* @author Mygod
@@ -41,7 +42,7 @@ class ShadowsocksNotification(private val service: BaseService, profileName: Str
.setSmallIcon(R.drawable.ic_stat_shadowsocks)
.addAction(R.drawable.ic_navigation_close, service.getString(R.string.stop),
PendingIntent.getBroadcast(service, 0, new Intent(Action.CLOSE), 0))
ShadowsocksApplication.profileManager.getAllProfiles match {
app.profileManager.getAllProfiles match {
case Some(profiles) => if (profiles.length > 1)
builder.addAction(R.drawable.ic_action_settings, service.getString(R.string.quick_switch),
PendingIntent.getActivity(service, 0, new Intent(Action.QUICK_SWITCH), 0))
@@ -8,6 +8,7 @@ import android.view.{LayoutInflater, View, ViewGroup}
import android.widget.CheckedTextView
import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.utils.Utils
import com.github.shadowsocks.ShadowsocksApplication.app
/**
* Created by Lucas on 3/10/16.
@@ -27,18 +28,18 @@ class ShadowsocksQuickSwitchActivity extends AppCompatActivity {
def bind(item: Profile) {
this.item = item
text.setText(item.name)
text.setChecked(item.id == ShadowsocksApplication.profileId)
text.setChecked(item.id == app.profileId)
}
def onClick(v: View) {
ShadowsocksApplication.switchProfile(item.id)
app.switchProfile(item.id)
Utils.startSsService(ShadowsocksQuickSwitchActivity.this)
finish
}
}
private class ProfilesAdapter extends RecyclerView.Adapter[ProfileViewHolder] {
val profiles = ShadowsocksApplication.profileManager.getAllProfiles.getOrElse(List.empty[Profile])
val profiles = app.profileManager.getAllProfiles.getOrElse(List.empty[Profile])
def getItemCount = profiles.length
@@ -66,8 +67,8 @@ class ShadowsocksQuickSwitchActivity extends AppCompatActivity {
profilesList.setLayoutManager(lm)
profilesList.setItemAnimator(new DefaultItemAnimator)
profilesList.setAdapter(profilesAdapter)
if (ShadowsocksApplication.profileId >= 0) lm.scrollToPosition(profilesAdapter.profiles.zipWithIndex.collectFirst {
case (profile, i) if profile.id == ShadowsocksApplication.profileId => i + 1
if (app.profileId >= 0) lm.scrollToPosition(profilesAdapter.profiles.zipWithIndex.collectFirst {
case (profile, i) if profile.id == app.profileId => i + 1
}.getOrElse(0))
}
}
@@ -40,13 +40,21 @@
package com.github.shadowsocks
import android.app.{Activity, KeyguardManager}
import android.content.{Intent, IntentFilter, Context, BroadcastReceiver}
import android.content.{BroadcastReceiver, Context, Intent, IntentFilter}
import android.net.VpnService
import android.os.{Bundle, Handler}
import android.util.Log
import com.github.shadowsocks.utils.ConfigUtils
import com.github.shadowsocks.ShadowsocksApplication.app
object ShadowsocksRunnerActivity {
private final val TAG = "ShadowsocksRunnerActivity"
private final val REQUEST_CONNECT = 1
}
class ShadowsocksRunnerActivity extends Activity with ServiceBoundContext {
import ShadowsocksRunnerActivity._
val handler = new Handler()
// Variables
@@ -57,16 +65,16 @@ class ShadowsocksRunnerActivity extends Activity with ServiceBoundContext {
}
def startBackgroundService() {
if (ShadowsocksApplication.isVpnEnabled) {
if (app.isNatEnabled) {
bgService.use(ConfigUtils.loadFromSharedPreferences)
finish()
} else {
val intent = VpnService.prepare(ShadowsocksRunnerActivity.this)
if (intent != null) {
startActivityForResult(intent, Shadowsocks.REQUEST_CONNECT)
startActivityForResult(intent, REQUEST_CONNECT)
} else {
onActivityResult(Shadowsocks.REQUEST_CONNECT, Activity.RESULT_OK, null)
onActivityResult(REQUEST_CONNECT, Activity.RESULT_OK, null)
}
} else {
bgService.use(ConfigUtils.load(ShadowsocksApplication.settings))
finish()
}
}
@@ -90,7 +98,7 @@ class ShadowsocksRunnerActivity extends Activity with ServiceBoundContext {
override def onDestroy() {
super.onDestroy()
deattachService()
detachService()
if (receiver != null) {
unregisterReceiver(receiver)
receiver = null
@@ -101,10 +109,10 @@ class ShadowsocksRunnerActivity extends Activity with ServiceBoundContext {
resultCode match {
case Activity.RESULT_OK =>
if (bgService != null) {
bgService.use(ConfigUtils.load(ShadowsocksApplication.settings))
bgService.use(ConfigUtils.loadFromSharedPreferences)
}
case _ =>
Log.e(Shadowsocks.TAG, "Failed to start VpnService")
Log.e(TAG, "Failed to start VpnService")
}
finish()
}
@@ -44,6 +44,7 @@ import android.content.Intent
import android.net.VpnService
import android.os.{IBinder, Handler}
import com.github.shadowsocks.utils.ConfigUtils
import com.github.shadowsocks.ShadowsocksApplication.app
class ShadowsocksRunnerService extends Service with ServiceBoundContext {
val handler = new Handler()
@@ -57,15 +58,13 @@ class ShadowsocksRunnerService extends Service with ServiceBoundContext {
}
def startBackgroundService() {
if (ShadowsocksApplication.isVpnEnabled) {
if (app.isNatEnabled) bgService.use(ConfigUtils.loadFromSharedPreferences) else {
val intent = VpnService.prepare(ShadowsocksRunnerService.this)
if (intent == null) {
if (bgService != null) {
bgService.use(ConfigUtils.load(ShadowsocksApplication.settings))
bgService.use(ConfigUtils.loadFromSharedPreferences)
}
}
} else {
bgService.use(ConfigUtils.load(ShadowsocksApplication.settings))
}
stopSelf()
}
@@ -77,6 +76,6 @@ class ShadowsocksRunnerService extends Service with ServiceBoundContext {
override def onDestroy() {
super.onDestroy()
deattachService()
detachService()
}
}
@@ -12,13 +12,14 @@ import android.webkit.{WebView, WebViewClient}
import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.preferences._
import com.github.shadowsocks.utils.{Key, Utils}
import com.github.shadowsocks.ShadowsocksApplication.app
object ShadowsocksSettings {
// Constants
val PREFS_NAME = "Shadowsocks"
val PROXY_PREFS = Array(Key.profileName, Key.proxy, Key.remotePort, Key.localPort, Key.sitekey, Key.encMethod,
private final val TAG = "ShadowsocksSettings"
private val PROXY_PREFS = Array(Key.profileName, Key.proxy, Key.remotePort, Key.localPort, Key.sitekey, Key.encMethod,
Key.isAuth)
val FEATURE_PREFS = Array(Key.route, Key.isProxyApps, Key.isUdpDns, Key.isIpv6)
private val FEATURE_PREFS = Array(Key.route, Key.isProxyApps, Key.isUdpDns, Key.isIpv6)
// Helper functions
def updateDropDownPreference(pref: Preference, value: String) {
@@ -80,8 +81,19 @@ class ShadowsocksSettings extends PreferenceFragment with OnSharedPreferenceChan
false
})
val switch = findPreference(Key.isAutoConnect).asInstanceOf[SwitchPreference]
switch.setOnPreferenceChangeListener((_, value) => {
BootReceiver.setEnabled(activity, value.asInstanceOf[Boolean])
true
})
if (getPreferenceManager.getSharedPreferences.getBoolean(Key.isAutoConnect, false)) {
BootReceiver.setEnabled(activity, true)
getPreferenceManager.getSharedPreferences.edit.remove(Key.isAutoConnect).apply
}
switch.setChecked(BootReceiver.getEnabled(activity))
findPreference("recovery").setOnPreferenceClickListener((preference: Preference) => {
ShadowsocksApplication.track(Shadowsocks.TAG, "reset")
app.track(TAG, "reset")
activity.recovery()
true
})
@@ -89,13 +101,13 @@ class ShadowsocksSettings extends PreferenceFragment with OnSharedPreferenceChan
val flush = findPreference("flush_dnscache")
if (Build.VERSION.SDK_INT < 17) flush.setSummary(R.string.flush_dnscache_summary)
flush.setOnPreferenceClickListener(_ => {
ShadowsocksApplication.track(Shadowsocks.TAG, "flush_dnscache")
app.track(TAG, "flush_dnscache")
activity.flushDnsCache()
true
})
findPreference("about").setOnPreferenceClickListener((preference: Preference) => {
ShadowsocksApplication.track(Shadowsocks.TAG, "about")
app.track(TAG, "about")
val web = new WebView(activity)
web.loadUrl("file:///android_asset/pages/about.html")
web.setWebViewClient(new WebViewClient() {
@@ -110,7 +122,7 @@ class ShadowsocksSettings extends PreferenceFragment with OnSharedPreferenceChan
})
new AlertDialog.Builder(activity)
.setTitle(getString(R.string.about_title).formatLocal(Locale.ENGLISH, ShadowsocksApplication.getVersionName))
.setTitle(getString(R.string.about_title).formatLocal(Locale.ENGLISH, app.getVersionName))
.setNegativeButton(getString(android.R.string.ok), null)
.setView(web)
.create()
@@ -121,18 +133,18 @@ class ShadowsocksSettings extends PreferenceFragment with OnSharedPreferenceChan
override def onResume {
super.onResume()
isProxyApps.setChecked(ShadowsocksApplication.settings.getBoolean(Key.isProxyApps, false)) // update
isProxyApps.setChecked(app.settings.getBoolean(Key.isProxyApps, false)) // update
}
override def onDestroy {
super.onDestroy()
ShadowsocksApplication.settings.unregisterOnSharedPreferenceChangeListener(this)
app.settings.unregisterOnSharedPreferenceChangeListener(this)
}
def onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) = key match {
def onSharedPreferenceChanged(pref: SharedPreferences, key: String) = key match {
case Key.isNAT =>
activity.handler.post(() => {
activity.deattachService
activity.detachService
activity.attachService
})
case _ =>
@@ -144,7 +156,7 @@ class ShadowsocksSettings extends PreferenceFragment with OnSharedPreferenceChan
for (name <- Key.isNAT #:: PROXY_PREFS.toStream #::: FEATURE_PREFS.toStream) {
val pref = findPreference(name)
if (pref != null) pref.setEnabled(enabled &&
(name != Key.isProxyApps || Utils.isLollipopOrAbove || !ShadowsocksApplication.isVpnEnabled))
(name != Key.isProxyApps || Utils.isLollipopOrAbove || app.isNatEnabled))
}
}
@@ -45,13 +45,12 @@ import java.util.Locale
import android.content._
import android.content.pm.PackageManager.NameNotFoundException
import android.content.pm.{PackageInfo, PackageManager}
import android.net.VpnService
import android.os._
import android.util.Log
import com.github.shadowsocks.aidl.Config
import com.github.shadowsocks.utils._
import org.apache.commons.net.util.SubnetUtils
import com.github.shadowsocks.ShadowsocksApplication.app
import scala.collection.mutable.ArrayBuffer
@@ -69,27 +68,6 @@ class ShadowsocksVpnService extends VpnService with BaseService {
var pdnsdProcess: Process = _
var tun2socksProcess: Process = _
def isByass(net: SubnetUtils): Boolean = {
val info = net.getInfo
info.isInRange(config.proxy)
}
def isPrivateA(a: Int): Boolean = {
if (a == 10 || a == 192 || a == 172) {
true
} else {
false
}
}
def isPrivateB(a: Int, b: Int): Boolean = {
if (a == 10 || (a == 192 && b == 168) || (a == 172 && b >= 16 && b < 32)) {
true
} else {
false
}
}
override def onBind(intent: Intent): IBinder = {
val action = intent.getAction
if (VpnService.SERVICE_INTERFACE == action) {
@@ -121,7 +99,7 @@ class ShadowsocksVpnService extends VpnService with BaseService {
// channge the state
changeState(State.STOPPING)
ShadowsocksApplication.track(TAG, "stop")
app.track(TAG, "stop")
// reset VPN
killProcesses()
@@ -135,18 +113,6 @@ class ShadowsocksVpnService extends VpnService with BaseService {
super.stopRunner(stopService)
}
def getVersionName: String = {
var version: String = null
try {
val pi: PackageInfo = getPackageManager.getPackageInfo(getPackageName, 0)
version = pi.versionName
} catch {
case e: PackageManager.NameNotFoundException =>
version = "Package name not found"
}
version
}
def killProcesses() {
if (sslocalProcess != null) {
sslocalProcess.destroy()
@@ -181,13 +147,13 @@ class ShadowsocksVpnService extends VpnService with BaseService {
return
}
ShadowsocksApplication.track(TAG, "start")
app.track(TAG, "start")
changeState(State.CONNECTING)
ThrowableFuture {
Utils.ThrowableFuture {
if (config.proxy == "198.199.101.152") {
val holder = ShadowsocksApplication.containerHolder
val holder = app.containerHolder
try {
this.config = ConfigUtils.getPublicConfig(getBaseContext, holder.getContainer, config)
} catch {
@@ -216,7 +182,10 @@ class ShadowsocksVpnService extends VpnService with BaseService {
resolved = true
}
if (resolved && handleConnection) {
if (!resolved) {
changeState(State.STOPPED, getString(R.string.invalid_server))
stopRunner(true)
} else if (handleConnection) {
changeState(State.CONNECTED)
notification = new ShadowsocksNotification(this, config.profileName)
} else {
@@ -308,8 +277,8 @@ class ShadowsocksVpnService extends VpnService with BaseService {
val ipv6 = if (config.isIpv6) "" else "reject = ::/0;"
val conf = {
if (config.route == Route.BYPASS_CHN || config.route == Route.BYPASS_LAN_CHN) {
val reject = ConfigUtils.getRejectList(getContext)
val blackList = ConfigUtils.getBlackList(getContext)
val reject = ConfigUtils.getRejectList(this)
val blackList = ConfigUtils.getBlackList(this)
ConfigUtils.PDNSD_DIRECT.formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir,
"0.0.0.0", 8153, reject, blackList, 8163, ipv6)
} else {
@@ -327,8 +296,6 @@ class ShadowsocksVpnService extends VpnService with BaseService {
pdnsdProcess = new GuardedProcess(cmd.split(" ").toSeq).start()
}
override def getContext = getBaseContext
def startVpn(): Int = {
val builder = new Builder()
@@ -432,8 +399,4 @@ class ShadowsocksVpnService extends VpnService with BaseService {
}
false
}
override def getTag = TAG
override def getServiceMode = Mode.VPN
}
@@ -47,6 +47,7 @@ import android.view.{LayoutInflater, View, ViewGroup}
import android.widget.{CheckedTextView, Switch}
import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.utils.TaskerSettings
import com.github.shadowsocks.ShadowsocksApplication.app
/**
* @author CzBiX
@@ -82,7 +83,7 @@ class TaskerActivity extends AppCompatActivity {
}
private class ProfilesAdapter extends RecyclerView.Adapter[ProfileViewHolder] {
val profiles = ShadowsocksApplication.profileManager.getAllProfiles.getOrElse(List.empty[Profile])
val profiles = app.profileManager.getAllProfiles.getOrElse(List.empty[Profile])
def getItemCount = 1 + profiles.length
def onBindViewHolder(vh: ProfileViewHolder, i: Int) = i match {
case 0 => vh.bindDefault
@@ -40,6 +40,7 @@ package com.github.shadowsocks
import android.content.{BroadcastReceiver, Context, Intent}
import com.github.shadowsocks.utils.{TaskerSettings, Utils}
import com.github.shadowsocks.ShadowsocksApplication.app
/**
* @author CzBiX
@@ -47,9 +48,9 @@ import com.github.shadowsocks.utils.{TaskerSettings, Utils}
class TaskerReceiver extends BroadcastReceiver {
override def onReceive(context: Context, intent: Intent) {
val settings = TaskerSettings.fromIntent(intent)
val switched = ShadowsocksApplication.profileManager.getProfile(settings.profileId) match {
val switched = app.profileManager.getProfile(settings.profileId) match {
case Some(p) =>
ShadowsocksApplication.switchProfile(settings.profileId)
app.switchProfile(settings.profileId)
true
case _ => false
}
@@ -41,10 +41,15 @@ package com.github.shadowsocks.database
import android.content.SharedPreferences
import android.util.Log
import com.github.shadowsocks._
import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.utils.Key
object ProfileManager {
private final val TAG = "ProfileManager"
}
class ProfileManager(settings: SharedPreferences, dbHelper: DBHelper) {
import ProfileManager._
var profileAddedListener: Profile => Any = _
def setProfileAddedListener(listener: Profile => Any) = this.profileAddedListener = listener
@@ -53,7 +58,7 @@ class ProfileManager(settings: SharedPreferences, dbHelper: DBHelper) {
try {
val profile = if (p == null) new Profile else p
profile.id = 0
ShadowsocksApplication.currentProfile match {
app.currentProfile match {
case Some(oldProfile) =>
// Copy Feature Settings from old profile
profile.route = oldProfile.route
@@ -72,7 +77,7 @@ class ProfileManager(settings: SharedPreferences, dbHelper: DBHelper) {
profile
} catch {
case ex: Exception =>
Log.e(Shadowsocks.TAG, "addProfile", ex)
Log.e(TAG, "addProfile", ex)
p
}
}
@@ -83,7 +88,7 @@ class ProfileManager(settings: SharedPreferences, dbHelper: DBHelper) {
true
} catch {
case ex: Exception =>
Log.e(Shadowsocks.TAG, "updateProfile", ex)
Log.e(TAG, "updateProfile", ex)
false
}
}
@@ -96,7 +101,7 @@ class ProfileManager(settings: SharedPreferences, dbHelper: DBHelper) {
}
} catch {
case ex: Exception =>
Log.e(Shadowsocks.TAG, "getProfile", ex)
Log.e(TAG, "getProfile", ex)
None
}
}
@@ -107,7 +112,7 @@ class ProfileManager(settings: SharedPreferences, dbHelper: DBHelper) {
true
} catch {
case ex: Exception =>
Log.e(Shadowsocks.TAG, "delProfile", ex)
Log.e(TAG, "delProfile", ex)
false
}
}
@@ -118,7 +123,7 @@ class ProfileManager(settings: SharedPreferences, dbHelper: DBHelper) {
if (result.size == 1) Option(result.get(0)) else None
} catch {
case ex: Exception =>
Log.e(Shadowsocks.TAG, "getAllProfiles", ex)
Log.e(TAG, "getAllProfiles", ex)
None
}
}
@@ -129,7 +134,7 @@ class ProfileManager(settings: SharedPreferences, dbHelper: DBHelper) {
Option(dbHelper.profileDao.query(dbHelper.profileDao.queryBuilder.orderBy("userOrder", true).prepare).toList)
} catch {
case ex: Exception =>
Log.e(Shadowsocks.TAG, "getAllProfiles", ex)
Log.e(TAG, "getAllProfiles", ex)
None
}
}
@@ -1,17 +0,0 @@
package com.github
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import scala.util.{Failure, Try}
/**
* @author Mygod
*/
package object shadowsocks {
private val handleFailure: Try[_] => Unit = {
case Failure(e) => e.printStackTrace()
case _ =>
}
def ThrowableFuture[T](f: => T) = Future(f) onComplete handleFailure
}
@@ -39,11 +39,12 @@
package com.github.shadowsocks.utils
import android.content.{SharedPreferences, Context}
import com.github.shadowsocks.{R, ShadowsocksApplication}
import com.google.android.gms.tagmanager.Container
import android.content.Context
import com.github.kevinsawicki.http.HttpRequest
import com.github.shadowsocks.R
import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.aidl.Config
import com.google.android.gms.tagmanager.Container
object ConfigUtils {
val SHADOWSOCKS = "{\"server\": \"%s\", \"server_port\": %d, \"local_port\": %d, \"password\": \"%s\", \"method\":\"%s\", \"timeout\": %d}"
@@ -96,49 +97,6 @@ object ConfigUtils {
|}
""".stripMargin
val PDNSD_BYPASS =
"""
|global {
| perm_cache = 2048;
| cache_dir = "%s";
| server_ip = %s;
| server_port = %d;
| query_method = tcp_only;
| run_ipv4 = on;
| min_ttl = 15m;
| max_ttl = 1w;
| timeout = 10;
| daemon = off;
|}
|
|server {
| label = "china-servers";
| ip = 114.114.114.114, 223.5.5.5;
| uptest = none;
| preset = on;
| include = %s;
| policy = excluded;
| timeout = 2;
|}
|
|server {
| label = "local-server";
| ip = 127.0.0.1;
| uptest = none;
| preset = on;
| port = %d;
| timeout = 5;
|}
|
|rr {
| name=localhost;
| reverse=on;
| a=127.0.0.1;
| owner=localhost;
| soa=localhost,root.localhost,42,86400,900,86400,86400;
|}
""".stripMargin
val PDNSD_DIRECT =
"""
|global {
@@ -195,14 +153,14 @@ object ConfigUtils {
}
def refresh(context: Context) {
val holder = ShadowsocksApplication.containerHolder
val holder = app.containerHolder
if (holder != null) holder.refresh()
}
def getRejectList(context: Context): String = {
val default = context.getString(R.string.reject)
try {
val container = ShadowsocksApplication.containerHolder.getContainer
val container = app.containerHolder.getContainer
val update = container.getString("reject")
if (update == null || update.isEmpty) default else update
} catch {
@@ -213,7 +171,7 @@ object ConfigUtils {
def getBlackList(context: Context): String = {
val default = context.getString(R.string.black_list)
try {
val container = ShadowsocksApplication.containerHolder.getContainer
val container = app.containerHolder.getContainer
val update = container.getString("black_list")
if (update == null || update.isEmpty) default else update
} catch {
@@ -242,24 +200,24 @@ object ConfigUtils {
config.profileName, host, password, method, config.proxiedAppString, config.route, port, config.localPort, 0)
}
def load(settings: SharedPreferences): Config = {
val isProxyApps = settings.getBoolean(Key.isProxyApps, false)
val isBypassApps = settings.getBoolean(Key.isBypassApps, false)
val isUdpDns = settings.getBoolean(Key.isUdpDns, false)
val isAuth = settings.getBoolean(Key.isAuth, false)
val isIpv6 = settings.getBoolean(Key.isIpv6, false)
def loadFromSharedPreferences = {
val isProxyApps = app.settings.getBoolean(Key.isProxyApps, false)
val isBypassApps = app.settings.getBoolean(Key.isBypassApps, false)
val isUdpDns = app.settings.getBoolean(Key.isUdpDns, false)
val isAuth = app.settings.getBoolean(Key.isAuth, false)
val isIpv6 = app.settings.getBoolean(Key.isIpv6, false)
val profileName = settings.getString(Key.profileName, "default")
val proxy = settings.getString(Key.proxy, "127.0.0.1")
val sitekey = settings.getString(Key.sitekey, "default")
val encMethod = settings.getString(Key.encMethod, "table")
val route = settings.getString(Key.route, "all")
val profileName = app.settings.getString(Key.profileName, "default")
val proxy = app.settings.getString(Key.proxy, "127.0.0.1")
val sitekey = app.settings.getString(Key.sitekey, "default")
val encMethod = app.settings.getString(Key.encMethod, "table")
val route = app.settings.getString(Key.route, "all")
val remotePort = settings.getInt(Key.remotePort, 1984)
val localPort = settings.getInt(Key.localPort, 1984)
val proxiedAppString = settings.getString(Key.proxied, "")
val remotePort = app.settings.getInt(Key.remotePort, 1984)
val localPort = app.settings.getInt(Key.localPort, 1984)
val proxiedAppString = app.settings.getString(Key.proxied, "")
val profileId = settings.getInt(Key.profileId, -1)
val profileId = app.settings.getInt(Key.profileId, -1)
new Config(isProxyApps, isBypassApps, isUdpDns, isAuth, isIpv6, profileName, proxy, sitekey, encMethod,
proxiedAppString, route, remotePort, localPort, profileId)
@@ -63,10 +63,6 @@ object Console {
.open()
}
def runCommand(command: String) {
runCommand(Array(command))
}
def runCommand(commands: Array[String]) {
val shell = openShell()
shell.addCommand(commands, 0, ((commandCode: Int, exitCode: Int, output: util.List[String]) =>
@@ -44,17 +44,9 @@ object Executable {
val PDNSD = "pdnsd"
val SS_LOCAL = "ss-local"
val SS_TUNNEL = "ss-tunnel"
val IPTABLES = "iptables"
val TUN2SOCKS = "tun2socks"
}
object Msg {
val CONNECT_FINISH = 1
val CONNECT_SUCCESS = 2
val CONNECT_FAIL = 3
val VPN_ERROR = 6
}
object Key {
val profileId = "profileId"
val profileName = "profileName"
@@ -63,9 +55,7 @@ object Key {
val isNAT = "isNAT"
val route = "route"
val stat = "stat"
val isRunning = "isRunning"
val isAutoConnect = "isAutoConnect"
val isProxyApps = "isProxyApps"
@@ -83,17 +73,6 @@ object Key {
val profileTip = "profileTip"
}
object Scheme {
val APP = "app://"
val PROFILE = "profile://"
val SS = "ss"
}
object Mode {
val NAT = 0
val VPN = 1
}
object State {
val CONNECTING = 1
val CONNECTED = 2
@@ -47,7 +47,7 @@ object Parser {
private val pattern = "(?i)ss://([A-Za-z0-9+-/=_]+)".r
private val decodedPattern = "(?i)^((.+?)(-auth)??:(.*)@(.+?):(\\d+?))$".r
def findAll(data: CharSequence) = pattern.findAllMatchIn(data).map(m => try
def findAll(data: CharSequence) = pattern.findAllMatchIn(if (data == null) "" else data).map(m => try
decodedPattern.findFirstMatchIn(new String(Base64.decode(m.group(1), Base64.NO_PADDING), "UTF-8")) match {
case Some(ss) =>
val profile = new Profile
@@ -60,9 +60,9 @@ object Parser {
profile
case _ => null
}
catch {
case ex: Exception =>
Log.e(TAG, "parser error: " + m.source, ex)// Ignore
null
}).filter(_ != null)
catch {
case ex: Exception =>
Log.e(TAG, "parser error: " + m.source, ex)// Ignore
null
}).filter(_ != null)
}
@@ -40,7 +40,8 @@ package com.github.shadowsocks.utils
import android.content.{Context, Intent}
import android.os.Bundle
import com.github.shadowsocks.{R, ShadowsocksApplication}
import com.github.shadowsocks.R
import com.github.shadowsocks.ShadowsocksApplication.app
import com.twofortyfouram.locale.api.{Intent => ApiIntent}
object TaskerSettings {
@@ -49,8 +50,6 @@ object TaskerSettings {
def fromIntent(intent: Intent) = new TaskerSettings(if (intent.hasExtra(ApiIntent.EXTRA_BUNDLE))
intent.getBundleExtra(ApiIntent.EXTRA_BUNDLE) else Bundle.EMPTY)
def fromBundle(bundle: Bundle) = new TaskerSettings(bundle)
}
class TaskerSettings(bundle: Bundle) {
@@ -64,7 +63,7 @@ class TaskerSettings(bundle: Bundle) {
if (!switchOn) bundle.putBoolean(KEY_SWITCH_ON, false)
if (profileId >= 0) bundle.putInt(KEY_PROFILE_ID, profileId)
new Intent().putExtra(ApiIntent.EXTRA_BUNDLE, bundle).putExtra(ApiIntent.EXTRA_STRING_BLURB,
ShadowsocksApplication.profileManager.getProfile(profileId) match {
app.profileManager.getProfile(profileId) match {
case Some(p) => context.getString(if (switchOn) R.string.start_service else R.string.stop_service, p.name)
case None => context.getString(if (switchOn) R.string.start_service_default else R.string.stop)
})
@@ -2,7 +2,8 @@ package com.github.shadowsocks.utils
import java.text.DecimalFormat
import com.github.shadowsocks.{R, ShadowsocksApplication}
import com.github.shadowsocks.R
import com.github.shadowsocks.ShadowsocksApplication.app
object TrafficMonitor {
// Bytes per second
@@ -28,7 +29,7 @@ object TrafficMonitor {
n /= 1024
i = i + 1
}
if (i < 0) size + " " + ShadowsocksApplication.instance.getResources.getQuantityString(R.plurals.bytes, size.toInt)
if (i < 0) size + " " + app.getResources.getQuantityString(R.plurals.bytes, size.toInt)
else numberFormat.format(n) + ' ' + units(i)
}
@@ -38,35 +38,41 @@
*/
package com.github.shadowsocks.utils
import java.io._
import java.net._
import java.security.MessageDigest
import android.animation.{Animator, AnimatorListenerAdapter}
import android.app.ActivityManager
import android.content.pm.{ApplicationInfo, PackageManager}
import android.content.pm.PackageManager
import android.content.{Context, Intent}
import android.graphics._
import android.graphics.drawable.{BitmapDrawable, Drawable}
import android.os.Build
import android.provider.Settings
import android.support.v4.content.ContextCompat
import android.util.{Base64, DisplayMetrics, Log}
import android.view.View.MeasureSpec
import android.view.{Gravity, View, Window}
import android.widget.Toast
import com.github.shadowsocks.{BuildConfig, ShadowsocksApplication, ShadowsocksRunnerService}
import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.{BuildConfig, ShadowsocksRunnerService}
import org.xbill.DNS._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import scala.util.{Failure, Try}
object Utils {
private val TAG = "Shadowsocks"
val TAG: String = "Shadowsocks"
val DEFAULT_IPTABLES: String = "/system/bin/iptables"
val ALTERNATIVE_IPTABLES: String = "iptables"
var initialized: Boolean = false
var hasRedirectSupport: Int = -1
var iptables: String = null
/**
* Check the system's iptables
* Default to use the app's iptables
*/
private val DEFAULT_IPTABLES = "/system/bin/iptables"
private val ALTERNATIVE_IPTABLES = "iptables"
lazy val iptables = if (Console.isRoot) {
val lines = Console.runRootCommand(DEFAULT_IPTABLES + " --version", DEFAULT_IPTABLES + " -L -t nat -n")
if (Console.isRoot && lines != null && !lines.contains("OUTPUT") || !lines.contains("v1.4.")) ALTERNATIVE_IPTABLES
else DEFAULT_IPTABLES
} else DEFAULT_IPTABLES
def isLollipopOrAbove: Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
@@ -86,31 +92,6 @@ object Utils {
* round or floor depending on whether you are using offsets(floor) or
* widths(round)
*/
def dpToPx(context: Context, dp: Float): Float = {
val density = context.getResources.getDisplayMetrics.density
dp * density
}
def pxToDp(context: Context, px: Float): Float = {
val density = context.getResources.getDisplayMetrics.density
px / density
}
def getBitmap(text: String, width: Int, height: Int, background: Int): Bitmap = {
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
val size = bitmap.getHeight / 4
val canvas = new Canvas(bitmap)
canvas.drawColor(background)
val paint = new Paint()
paint.setColor(Color.WHITE)
paint.setTextSize(size)
val bounds = new Rect()
paint.getTextBounds(text, 0, text.length, bounds)
canvas
.drawText(text, (bitmap.getWidth - bounds.width()) / 2,
bitmap.getHeight - (bitmap.getHeight - bounds.height()) / 2, paint)
bitmap
}
// Based on: http://stackoverflow.com/a/21026866/2245107
def positionToast(toast: Toast, view: View, window: Window, offsetX: Int = 0, offsetY: Int = 0) = {
@@ -165,25 +146,6 @@ object Utils {
context.sendBroadcast(disableIntent)
}
def isServiceStarted(name: String, context: Context): Boolean = {
import scala.collection.JavaConversions._
val activityManager = context
.getSystemService(Context.ACTIVITY_SERVICE)
.asInstanceOf[ActivityManager]
val services = activityManager.getRunningServices(Integer.MAX_VALUE)
if (services != null) {
for (service <- services) {
if (service.service.getClassName == name) {
return true
}
}
}
false
}
def resolve(host: String, addrType: Int): Option[String] = {
try {
val lookup = new Lookup(host, addrType)
@@ -240,31 +202,6 @@ object Utils {
private lazy val isNumericMethod = classOf[InetAddress].getMethod("isNumeric", classOf[String])
def isNumeric(address: String): Boolean = isNumericMethod.invoke(null, address).asInstanceOf[Boolean]
/**
* Get local IPv4 address
*/
def getIPv4Address: Option[String] = {
try {
val interfaces = NetworkInterface.getNetworkInterfaces
while (interfaces.hasMoreElements) {
val intf = interfaces.nextElement()
val addrs = intf.getInetAddresses
while (addrs.hasMoreElements) {
val addr = addrs.nextElement()
if (!addr.isLoopbackAddress && !addr.isLinkLocalAddress) {
if (addr.isInstanceOf[Inet4Address]) {
return Some(addr.getHostAddress.toUpperCase)
}
}
}
}
} catch {
case ex: Exception =>
Log.e(TAG, "Failed to get interfaces' addresses.", ex)
}
None
}
/**
* If there exists a valid IPv6 interface
*/
@@ -291,118 +228,8 @@ object Utils {
false
}
/**
* Check the system's iptables
* Default to use the app's iptables
*/
def checkIptables() {
if (!Console.isRoot) {
iptables = DEFAULT_IPTABLES
return
}
iptables = DEFAULT_IPTABLES
var compatible: Boolean = false
var version: Boolean = false
val lines = Console.runRootCommand(iptables + " --version", iptables + " -L -t nat -n")
if (lines == null) return
if (lines.contains("OUTPUT")) {
compatible = true
}
if (lines.contains("v1.4.")) {
version = true
}
if (!compatible || !version) {
iptables = ALTERNATIVE_IPTABLES
if (!new File(iptables).exists) iptables = "iptables"
}
}
def drawableToBitmap(drawable: Drawable): Bitmap = {
drawable match {
case d: BitmapDrawable =>
return d.getBitmap
case _ =>
}
val width = if (drawable.getIntrinsicWidth > 0) drawable.getIntrinsicWidth else 1
val height = if (drawable.getIntrinsicWidth > 0) drawable.getIntrinsicWidth else 1
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
val canvas = new Canvas(bitmap)
drawable.setBounds(0, 0, canvas.getWidth, canvas.getHeight)
drawable.draw(canvas)
bitmap
}
def getAppIcon(c: Context, packageName: String): Drawable = {
val pm: PackageManager = c.getPackageManager
val icon: Drawable = ContextCompat.getDrawable(c, android.R.drawable.sym_def_app_icon)
try {
pm.getApplicationIcon(packageName)
} catch {
case e: PackageManager.NameNotFoundException => icon
}
}
def getAppIcon(c: Context, uid: Int): Drawable = {
val pm: PackageManager = c.getPackageManager
val icon: Drawable = ContextCompat.getDrawable(c, android.R.drawable.sym_def_app_icon)
val packages: Array[String] = pm.getPackagesForUid(uid)
if (packages != null) {
if (packages.length >= 1) {
try {
val appInfo: ApplicationInfo = pm.getApplicationInfo(packages(0), 0)
return pm.getApplicationIcon(appInfo)
} catch {
case e: PackageManager.NameNotFoundException =>
Log.e(c.getPackageName, "No package found matching with the uid " + uid)
}
}
} else {
Log.e(c.getPackageName, "Package not found for uid " + uid)
}
icon
}
def getHasRedirectSupport: Boolean = {
if (hasRedirectSupport == -1) initHasRedirectSupported()
hasRedirectSupport == 1
}
def getIptables: String = {
if (iptables == null) checkIptables()
iptables
}
def initHasRedirectSupported() {
if (!Console.isRoot) return
hasRedirectSupport = 1
val command = Utils.getIptables + " -t nat -A OUTPUT -p udp --dport 54 -j REDIRECT --to 8154"
val lines = Console.runRootCommand(command, command.replace("-A", "-D"))
if (lines == null) return
if (lines.contains("No chain/target/match")) {
hasRedirectSupport = 0
}
}
def isInitialized: Boolean = {
initialized match {
case true => true
case _ =>
initialized = true
false
}
}
def startSsService(context: Context) {
val isInstalled: Boolean = ShadowsocksApplication.settings.getBoolean(ShadowsocksApplication.getVersionName, false)
val isInstalled: Boolean = app.settings.getBoolean(app.getVersionName, false)
if (!isInstalled) return
val intent = new Intent(context, classOf[ShadowsocksRunnerService])
@@ -413,6 +240,11 @@ object Utils {
val intent = new Intent(Action.CLOSE)
context.sendBroadcast(intent)
}
private val handleFailure: Try[_] => Unit = {
case Failure(e) => e.printStackTrace()
case _ =>
}
def ThrowableFuture[T](f: => T) = Future(f) onComplete handleFailure
}