Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
71b1d5fef1 | ||
|
|
5afb8b9cd4 | ||
|
|
650a01c9ce | ||
|
|
07798c7e56 | ||
|
|
8c3b4cc021 | ||
|
|
759d812347 | ||
|
|
2d4d5edff5 | ||
|
|
86f32ea67c | ||
|
|
461a0bda3f | ||
|
|
c878d4daa6 | ||
|
|
9be24d7408 | ||
|
|
28ea9dd807 | ||
|
|
c695847403 | ||
|
|
4a1f6fea98 | ||
|
|
535561cfe4 | ||
|
|
505e40eb3e | ||
|
|
a1871f792d | ||
|
|
0b7fb75b7a | ||
|
|
c52c33f3ac | ||
|
|
6cdb12de99 | ||
|
|
5d902be580 | ||
|
|
14267260bf | ||
|
|
5e0eed2953 | ||
|
|
8310e13be3 | ||
|
|
6b91d44c1c | ||
|
|
883b6349ab | ||
|
|
2c2e5790f5 | ||
|
|
4168329481 | ||
|
|
19be10fbdf | ||
|
|
70aa867c53 | ||
|
|
f4b18b6145 | ||
|
|
7845b7abdc | ||
|
|
5328f5851d | ||
|
|
1ddde094e3 | ||
|
|
ee7ff7d5dd | ||
|
|
0503eeac41 |
+2
-3
@@ -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
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Please check the [FAQ](https://github.com/shadowsocks/shadowsocks-android/wiki/FAQ) before submitting new issues.
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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";
|
||||
}
|
||||
@@ -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
@@ -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")
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 \
|
||||
|
||||
Submodule src/main/jni/shadowsocks-libev updated: 0d64f11f47...fd422008b7
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="proxied_apps_summary">Установить прокси для выбранных приложений</string>
|
||||
</resources>
|
||||
@@ -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 & 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">• Нажмите для выбора профиля;\n• Сдвиньте влево/вправо или нажмите влево
|
||||
чтобы удалить;\n• Подержите чтобы изменить порядок профилей.</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>
|
||||
@@ -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
@@ -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>
|
||||
|
||||
@@ -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()
|
||||
|
||||
+13
-7
@@ -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
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user