Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8c593b110 | ||
|
|
782af959a0 | ||
|
|
5b1432e4e8 | ||
|
|
7ca4e4ec74 | ||
|
|
655ef1eed6 | ||
|
|
04be60577a | ||
|
|
63d2ec7b02 | ||
|
|
06b6afd557 | ||
|
|
32f4d6af47 | ||
|
|
264b177b38 | ||
|
|
67a1c5460a | ||
|
|
a20b7821f2 | ||
|
|
069cc152ca | ||
|
|
7b57be669c | ||
|
|
2f051e9eed | ||
|
|
8d1d018fe1 | ||
|
|
3c5bc27750 | ||
|
|
6c047eca8e | ||
|
|
a31e753841 | ||
|
|
c94c509931 | ||
|
|
41417a7546 | ||
|
|
25cfb1623a | ||
|
|
c9ce515cca | ||
|
|
7802a50603 | ||
|
|
680a6594f6 | ||
|
|
b7367835b0 | ||
|
|
25bd5347eb | ||
|
|
d2a527ed0e | ||
|
|
07ab938719 | ||
|
|
b6bfd5bfa0 | ||
|
|
f9194df55d | ||
|
|
958d387b8c | ||
|
|
eee89774df | ||
|
|
8454975a13 | ||
|
|
69cb86a7c5 | ||
|
|
45ea903323 | ||
|
|
f7303e1564 | ||
|
|
99ff47bd5f | ||
|
|
820d71fb07 | ||
|
|
a9a6e1c1a6 | ||
|
|
364d873eba | ||
|
|
a722257aba | ||
|
|
80d93a84bd | ||
|
|
9b07239ced | ||
|
|
5c7174fc5e | ||
|
|
536fe92c83 | ||
|
|
29e6deabd0 | ||
|
|
e6b39444e8 | ||
|
|
1b480b0163 | ||
|
|
c7fc4095ff | ||
|
|
41d585ae22 |
@@ -28,6 +28,7 @@ android:
|
||||
components:
|
||||
- tools
|
||||
- build-tools-24.0.1
|
||||
- extra-android-m2repository
|
||||
- extra-google-m2repository
|
||||
|
||||
install:
|
||||
|
||||
@@ -15,7 +15,7 @@ A [shadowsocks](http://shadowsocks.org) client for Android, written in Scala.
|
||||
* Go 1.4+
|
||||
* Android SDK
|
||||
- Build Tools 24+ (as required by [`sbt-android`](https://github.com/scala-android/sbt-android))
|
||||
- Google Repository (see `build.sbt` for version)
|
||||
- Android Support Repository and Google Repository (see `build.sbt` for version)
|
||||
* Android NDK r12b+
|
||||
|
||||
### BUILD
|
||||
|
||||
@@ -10,9 +10,9 @@ scalaVersion := "2.11.8"
|
||||
|
||||
compileOrder in Compile := CompileOrder.JavaThenScala
|
||||
|
||||
javacOptions ++= Seq("-source", "1.6", "-target", "1.6")
|
||||
javacOptions ++= Seq("-source", "1.7", "-target", "1.7")
|
||||
|
||||
scalacOptions ++= Seq("-target:jvm-1.6", "-Xexperimental")
|
||||
scalacOptions ++= Seq("-target:jvm-1.7", "-Xexperimental")
|
||||
|
||||
ndkJavah in Android := List()
|
||||
|
||||
@@ -31,20 +31,19 @@ resolvers += Resolver.jcenterRepo
|
||||
useSupportVectors
|
||||
|
||||
libraryDependencies ++= Seq(
|
||||
"dnsjava" % "dnsjava" % "2.1.7",
|
||||
"com.android.support" % "design" % "24.2.0",
|
||||
"com.android.support" % "gridlayout-v7" % "24.2.0",
|
||||
"com.github.clans" % "fab" % "1.6.4",
|
||||
"com.github.jorgecastilloprz" % "fabprogresscircle" % "1.01",
|
||||
"com.github.kevinsawicki" % "http-request" % "6.0",
|
||||
"eu.chainfire" % "libsuperuser" % "1.0.0.201607041850",
|
||||
"net.glxn.qrgen" % "android" % "2.0",
|
||||
"com.google.android.gms" % "play-services-ads" % "9.4.0",
|
||||
"com.google.android.gms" % "play-services-analytics" % "9.4.0",
|
||||
"com.android.support" % "design" % "24.1.1",
|
||||
"com.android.support" % "gridlayout-v7" % "24.1.1",
|
||||
"com.android.support" % "cardview-v7" % "24.1.1",
|
||||
"com.github.jorgecastilloprz" % "fabprogresscircle" % "1.01",
|
||||
"com.j256.ormlite" % "ormlite-android" % "5.0",
|
||||
"com.twofortyfouram" % "android-plugin-api-for-locale" % "1.0.2",
|
||||
"com.github.clans" % "fab" % "1.6.4",
|
||||
"me.dm7.barcodescanner" % "zxing" % "1.9"
|
||||
"dnsjava" % "dnsjava" % "2.1.7",
|
||||
"eu.chainfire" % "libsuperuser" % "1.0.0.201607041850",
|
||||
"me.dm7.barcodescanner" % "zxing" % "1.9",
|
||||
"net.glxn.qrgen" % "android" % "2.0"
|
||||
)
|
||||
|
||||
proguardVersion in Android := "5.2.1"
|
||||
|
||||
+1
-1
Submodule kcptun/go updated: 70f0438db8...c3f75e654d
+1
-1
Submodule kcptun/kcptun updated: e8349a3063...c4054b07a6
+1
-1
@@ -1,4 +1,4 @@
|
||||
addSbtPlugin("org.scala-android" % "sbt-android" % "1.6.12")
|
||||
addSbtPlugin("org.scala-android" % "sbt-android" % "1.6.14")
|
||||
|
||||
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="146"
|
||||
android:versionName="3.0.4">
|
||||
android:versionCode="149"
|
||||
android:versionName="3.0.7">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
@@ -19,7 +19,7 @@
|
||||
android:required="false"/>
|
||||
|
||||
<uses-sdk
|
||||
android:minSdkVersion="16"
|
||||
android:minSdkVersion="19"
|
||||
android:targetSdkVersion="24"/>
|
||||
|
||||
<application
|
||||
@@ -109,7 +109,7 @@
|
||||
android:launchMode="singleInstance"
|
||||
android:excludeFromRecents="true"
|
||||
android:taskAffinity=""
|
||||
android:theme="@style/PopupTheme.QuickSwitch">
|
||||
android:theme="@style/Theme.Material.Dialog">
|
||||
<intent-filter>
|
||||
<action android:name="com.github.shadowsocks.QUICK_SWITCH" />
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
|
||||
@@ -559,8 +559,6 @@ MBEDTLS_SOURCES := $(wildcard $(LOCAL_PATH)/mbedtls/library/*.c)
|
||||
|
||||
LOCAL_SRC_FILES := $(MBEDTLS_SOURCES:$(LOCAL_PATH)/%=%)
|
||||
|
||||
LOCAL_LDLIBS := -ldl -llog
|
||||
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
|
||||
# Import cpufeatures
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
APP_ABI := armeabi-v7a x86
|
||||
APP_PLATFORM := android-16
|
||||
APP_STL := stlport_static
|
||||
NDK_TOOLCHAIN_VERSION := 4.9
|
||||
NDK_TOOLCHAIN_VERSION := clang
|
||||
|
||||
+1
-1
Submodule src/main/jni/pdnsd updated: b51fd4f65c...7a76962795
@@ -18,8 +18,8 @@
|
||||
android:text="@string/on"
|
||||
android:textSize="18sp"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:paddingLeft="60dp"
|
||||
android:paddingRight="8dp"
|
||||
android:paddingStart="60dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingBottom="12dp"
|
||||
android:layout_width="match_parent"
|
||||
@@ -30,8 +30,8 @@
|
||||
android:text="@string/bypass_apps"
|
||||
android:textSize="18sp"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:paddingLeft="60dp"
|
||||
android:paddingRight="8dp"
|
||||
android:paddingStart="60dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingBottom="12dp"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="48dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingRight="8dp"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:paddingTop="2dp"
|
||||
android:paddingBottom="2dp"/>
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
android:focusable="false"
|
||||
android:focusableInTouchMode="false"
|
||||
android:clickable="false"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginRight="8dp"/>
|
||||
android:layout_marginEnd="8dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -11,9 +11,9 @@
|
||||
android:layout_width="match_parent" android:layout_height="wrap_content"
|
||||
android:minHeight="?android:attr/listPreferredItemHeight" android:gravity="center_vertical"
|
||||
android:paddingStart="16dip" android:paddingEnd="16dip" android:focusable="true"
|
||||
android:paddingLeft="16dip" android:paddingRight="16dip"
|
||||
android:paddingTop="16dip" android:paddingBottom="16dip"
|
||||
android:background="@drawable/background_stat" android:orientation="vertical">
|
||||
android:background="@drawable/background_stat" android:orientation="vertical"
|
||||
android:nextFocusRight="@+id/fab">
|
||||
<LinearLayout android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<TextView android:text="@string/stat"
|
||||
@@ -33,12 +33,12 @@
|
||||
<android.support.v7.widget.GridLayout android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingEnd="72dip"
|
||||
android:paddingRight="72dip"
|
||||
app:columnCount="4">
|
||||
<TextView app:layout_column="0" style="@style/TextAppearance.AppCompat.Body2"
|
||||
android:ellipsize="marquee" android:text="@string/sent"
|
||||
android:layout_width="wrap_content" android:layout_height="wrap_content"
|
||||
android:layout_marginRight="24dp" android:layout_marginEnd="24dp" android:gravity="end"/>
|
||||
android:layout_marginEnd="24dp"
|
||||
android:gravity="end"/>
|
||||
<TextView app:layout_column="1" android:id="@+id/tx" android:textSize="14sp"
|
||||
android:textColor="?android:attr/textColorSecondary" android:ellipsize="marquee"
|
||||
android:layout_width="0dp" android:layout_height="wrap_content"
|
||||
@@ -53,7 +53,8 @@
|
||||
app:layout_gravity="fill_horizontal" android:gravity="end"/>
|
||||
<TextView app:layout_row="1" app:layout_column="0" style="@style/TextAppearance.AppCompat.Body2"
|
||||
android:layout_width="wrap_content" android:layout_height="wrap_content"
|
||||
android:layout_marginRight="24dp" android:layout_marginEnd="24dp" android:gravity="end"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:gravity="end"
|
||||
android:ellipsize="marquee" android:text="@string/received"/>
|
||||
<TextView app:layout_row="1" app:layout_column="1" android:id="@+id/rx" android:textSize="14sp"
|
||||
android:textColor="?android:attr/textColorSecondary" android:ellipsize="marquee"
|
||||
@@ -76,9 +77,7 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" android:elevation="6dp"
|
||||
android:layout_alignBottom="@+id/title_bar"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="@dimen/fab_margin_bottom">
|
||||
<android.support.design.widget.FloatingActionButton android:id="@+id/fab" android:layout_width="wrap_content"
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
app:menu_colorNormal="@color/material_accent_500"
|
||||
app:menu_colorPressed="@color/material_accent_700"
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:paddingStart="20dp"
|
||||
android:paddingEnd="?attr/dialogPreferredPadding"
|
||||
android:paddingLeft="20dp"
|
||||
android:paddingRight="?attr/dialogPreferredPadding"
|
||||
android:focusable="true"
|
||||
android:nextFocusRight="@+id/share">
|
||||
<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
@@ -23,7 +21,6 @@
|
||||
android:textColor="?android:attr/textColorAlertDialogListItem"
|
||||
android:gravity="center_vertical"
|
||||
android:drawableStart="?android:attr/listChoiceIndicatorSingle"
|
||||
android:drawableLeft="?android:attr/listChoiceIndicatorSingle"
|
||||
android:drawablePadding="20dp"
|
||||
android:maxLines="4"
|
||||
android:scrollHorizontally="false" />
|
||||
|
||||
@@ -1,24 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.v7.widget.CardView
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
app:cardElevation="5dp"
|
||||
app:cardBackgroundColor="#EEEEEE">
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
<include layout="@layout/toolbar_light_dark"/>
|
||||
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/profilesList"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<include layout="@layout/toolbar_light_dark"/>
|
||||
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/profilesList"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"/>
|
||||
</LinearLayout>
|
||||
</android.support.v7.widget.CardView>
|
||||
android:layout_height="wrap_content"/>
|
||||
</LinearLayout>
|
||||
|
||||
@@ -53,6 +53,8 @@
|
||||
<string name="bypass_apps_summary">Включите эту опцию для работы выбранных приложений в обход прокси</string>
|
||||
<string name="auto_connect">Авто Подключение</string>
|
||||
<string name="auto_connect_summary">Запускать Shadowsocks при включении</string>
|
||||
<string name="tcp_fastopen_summary">Переключение требует наличия ROOT прав</string>
|
||||
<string name="tcp_fastopen_summary_unsupported">Неподдерживаемая версия ядра: %s < 3.7.1</string>
|
||||
<string name="udp_dns">Проброс UDP</string>
|
||||
<string name="udp_dns_summary">Пробрасывать UDP пакеты на удалённый сервер</string>
|
||||
|
||||
@@ -92,8 +94,7 @@
|
||||
<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_summary">Работает только в режиме NAT. Требует наличия ROOT прав.</string>
|
||||
<string name="flush_dnscache_no_root">Очистка кэша DNS не работает без налиичя ROOT прав</string>
|
||||
<string name="flushing">Очистка</string>
|
||||
<string name="add_profile">Добавить Профиль</string>
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<resources>
|
||||
|
||||
<style name="Theme.Material.Dialog" parent="Theme.Material.DialogBase"/>
|
||||
<style name="Theme.Material.Dialog.Alert" parent="Theme.Material.Dialog.AlertBase"/>
|
||||
|
||||
</resources>
|
||||
@@ -32,6 +32,8 @@
|
||||
<string name="bypass_apps_summary">启用该选项,以使所选应用程序的流量不经过代理</string>
|
||||
<string name="auto_connect">自动连接</string>
|
||||
<string name="auto_connect_summary">随系统启动后台服务</string>
|
||||
<string name="tcp_fastopen_summary">切换需要 ROOT 权限</string>
|
||||
<string name="tcp_fastopen_summary_unsupported">不支持的内核版本:%s < 3.7.1</string>
|
||||
<string name="forward_success">后台服务已开始运行。</string>
|
||||
<string name="service_failed">无法连接远程服务器</string>
|
||||
<string name="invalid_server">服务器名无效</string>
|
||||
@@ -58,8 +60,7 @@
|
||||
<string name="about">关于</string>
|
||||
<string name="about_title">影梭(shadowsocks)%s</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_summary">只需在 NAT 模式下使用,需要 ROOT 权限</string>
|
||||
<string name="flush_dnscache_no_root">在没有 ROOT 权限的情况下无法刷新 DNS 缓存</string>
|
||||
<string name="flushing">刷新中……</string>
|
||||
<string name="share">分享</string>
|
||||
|
||||
@@ -33,6 +33,8 @@
|
||||
<string name="bypass_apps_summary">啟用此選項,會略過已選擇的應用程式</string>
|
||||
<string name="auto_connect">自動連線</string>
|
||||
<string name="auto_connect_summary">在裝置啟動時啟用 Shadowsocks</string>
|
||||
<string name="tcp_fastopen_summary">切換需要 ROOT 權限</string>
|
||||
<string name="tcp_fastopen_summary_unsupported">不支持的內核版本:%s < 3.7.1</string>
|
||||
<string name="forward_success">Shadowsocks 已啟動。</string>
|
||||
<string name="service_failed">連線至遠端伺服器失敗</string>
|
||||
<string name="invalid_server">伺服器名稱無效</string>
|
||||
@@ -59,8 +61,7 @@
|
||||
<string name="about">關於</string>
|
||||
<string name="about_title">Shadowsocks %s</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_summary">僅在 NAT 模式可用,需要 ROOT 權限</string>
|
||||
<string name="flush_dnscache_no_root">在無 ROOT 權限時清除 DNS 快取無法進行</string>
|
||||
<string name="flushing">清除中……</string>
|
||||
<string name="share">分享</string>
|
||||
|
||||
@@ -59,6 +59,8 @@
|
||||
<string name="bypass_apps_summary">Enable this option to bypass selected apps</string>
|
||||
<string name="auto_connect">Auto Connect</string>
|
||||
<string name="auto_connect_summary">Enable Shadowsocks on startup</string>
|
||||
<string name="tcp_fastopen_summary">Toggling requires ROOT permission</string>
|
||||
<string name="tcp_fastopen_summary_unsupported">Unsupported kernel version: %s < 3.7.1</string>
|
||||
<string name="udp_dns">UDP Forwarding</string>
|
||||
<string name="udp_dns_summary">Forward UDP packets to remote</string>
|
||||
|
||||
@@ -98,8 +100,7 @@
|
||||
<string name="share_message_nfc_disabled">Scan QR code\nAndroid Beam is disabled</string>
|
||||
<string name="turn_on_nfc">Turn on NFC & Android Beam</string>
|
||||
<string name="flush_dnscache">Flush DNS cache</string>
|
||||
<string name="flush_dnscache_summary">Only useful in NAT mode</string>
|
||||
<string name="flush_dnscache_summary_root">Only useful in NAT mode. Requires ROOT permission.</string>
|
||||
<string name="flush_dnscache_summary">Only useful in NAT mode. Requires ROOT permission.</string>
|
||||
<string name="flush_dnscache_no_root">Flush DNS cache cannot proceed without ROOT permission</string>
|
||||
<string name="flushing">Flushing</string>
|
||||
<string name="add_profile">Add Profile</string>
|
||||
|
||||
@@ -7,8 +7,6 @@
|
||||
</style>
|
||||
|
||||
<style name="Theme.Material" parent="Theme.AppCompat.Light.NoActionBar">
|
||||
<item name="android:alertDialogTheme">@style/Theme.Material.Dialog.Alert</item>
|
||||
<item name="android:dialogTheme">@style/Theme.Material.Dialog</item>
|
||||
<item name="android:navigationBarColor">@color/material_accent_700</item>
|
||||
<item name="colorAccent">@color/material_accent_500</item>
|
||||
<item name="colorButtonNormal">@color/material_accent_500</item>
|
||||
@@ -16,38 +14,11 @@
|
||||
<item name="colorPrimaryDark">@color/material_accent_700</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.Material.DialogBase" parent="Theme.AppCompat.Light.Dialog">
|
||||
<style name="Theme.Material.Dialog" parent="Theme.AppCompat.Light.Dialog">
|
||||
<item name="colorAccent">@color/material_accent_500</item>
|
||||
<item name="colorPrimary">@color/material_accent_500</item>
|
||||
<item name="colorPrimaryDark">@color/material_accent_700</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.Material.Dialog.AlertBase" parent="Theme.AppCompat.Light.Dialog.Alert">
|
||||
<item name="colorAccent">@color/material_accent_500</item>
|
||||
<item name="colorPrimary">@color/material_accent_500</item>
|
||||
<item name="colorPrimaryDark">@color/material_accent_700</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.Material.Dialog" parent="Theme.Material.DialogBase">
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.Material.Dialog.Alert" parent="Theme.Material.Dialog.AlertBase">
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
</style>
|
||||
|
||||
<style name="PopupTheme" parent="Theme.Material.Dialog">
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
<item name="android:windowSoftInputMode">stateAlwaysHidden</item>
|
||||
<item name="android:colorBackgroundCacheHint">@null</item>
|
||||
<item name="android:windowIsTranslucent">true</item>
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
</style>
|
||||
|
||||
<style name="PopupTheme.QuickSwitch" parent="PopupTheme">
|
||||
<item name="windowNoTitle">true</item>
|
||||
<item name="android:windowActionBar">false</item>
|
||||
<item name="android:windowAnimationStyle">@android:style/Animation.Translucent</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -94,7 +94,7 @@
|
||||
android:summary="@string/kcp_port_summary"
|
||||
android:title="@string/kcp_port"/>
|
||||
|
||||
<com.github.shadowsocks.preferences.SummaryEditTextPreference
|
||||
<com.github.shadowsocks.preferences.KcpCliPreference
|
||||
android:key="kcpcli"
|
||||
android:persistent="false"
|
||||
android:summary="@string/kcpcli_summary"
|
||||
@@ -110,6 +110,11 @@
|
||||
android:summary="@string/auto_connect_summary"
|
||||
android:title="@string/auto_connect"/>
|
||||
|
||||
<SwitchPreference
|
||||
android:key="tcp_fastopen"
|
||||
android:summary="@string/tcp_fastopen_summary"
|
||||
android:title="TCP Fast Open"/>
|
||||
|
||||
<SwitchPreference android:key="isNAT"
|
||||
android:title="@string/nat"
|
||||
android:summary="@string/nat_summary"/>
|
||||
@@ -117,7 +122,7 @@
|
||||
android:summary="@string/recovery_summary"/>
|
||||
<Preference android:key="flush_dnscache"
|
||||
android:title="@string/flush_dnscache"
|
||||
android:summary="@string/flush_dnscache_summary_root"/>
|
||||
android:summary="@string/flush_dnscache_summary"/>
|
||||
<Preference android:key="about" android:title="@string/about"/>
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
|
||||
package com.github.shadowsocks
|
||||
|
||||
import java.io.IOException
|
||||
import java.util.{Timer, TimerTask}
|
||||
|
||||
import android.app.Service
|
||||
@@ -48,7 +49,6 @@ import android.os.{Handler, RemoteCallbackList}
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
|
||||
import com.github.kevinsawicki.http.HttpRequest
|
||||
import com.github.shadowsocks.aidl.{IShadowsocksService, IShadowsocksServiceCallback}
|
||||
import com.github.shadowsocks.utils._
|
||||
@@ -60,6 +60,9 @@ trait BaseService extends Service {
|
||||
@volatile private var state = State.STOPPED
|
||||
@volatile protected var profile: Profile = _
|
||||
|
||||
case class NameNotResolvedException() extends IOException
|
||||
case class KcpcliParseException(cause: Throwable) extends Exception(cause)
|
||||
|
||||
var timer: Timer = _
|
||||
var trafficMonitorThread: TrafficMonitorThread = _
|
||||
|
||||
@@ -136,7 +139,7 @@ trait BaseService extends Service {
|
||||
false
|
||||
} else true
|
||||
|
||||
def connect() = if (profile.host == "198.199.101.152") try {
|
||||
def connect() = if (profile.host == "198.199.101.152") {
|
||||
val holder = app.containerHolder
|
||||
val container = holder.getContainer
|
||||
val url = container.getString("proxy_url")
|
||||
@@ -153,8 +156,6 @@ trait BaseService extends Service {
|
||||
profile.remotePort = proxy(1).trim.toInt
|
||||
profile.password = proxy(2).trim
|
||||
profile.method = proxy(3).trim
|
||||
} catch {
|
||||
case ex: Exception => stopRunner(true, getString(R.string.service_failed))
|
||||
}
|
||||
|
||||
def startRunner(profile: Profile) {
|
||||
@@ -188,7 +189,15 @@ trait BaseService extends Service {
|
||||
|
||||
if (profile.isMethodUnsafe) handler.post(() => Toast.makeText(this, R.string.method_unsafe, Toast.LENGTH_LONG).show)
|
||||
|
||||
Utils.ThrowableFuture(connect)
|
||||
Utils.ThrowableFuture(try connect catch {
|
||||
case _: NameNotResolvedException => stopRunner(true, getString(R.string.invalid_server))
|
||||
case exc: KcpcliParseException =>
|
||||
stopRunner(true, getString(R.string.service_failed) + ": " + exc.cause.getMessage)
|
||||
case exc: Throwable =>
|
||||
stopRunner(true, getString(R.string.service_failed) + ": " + exc.getMessage)
|
||||
exc.printStackTrace()
|
||||
app.track(exc)
|
||||
})
|
||||
}
|
||||
|
||||
def stopRunner(stopService: Boolean, msg: String = null) {
|
||||
|
||||
@@ -39,18 +39,35 @@
|
||||
|
||||
package com.github.shadowsocks
|
||||
|
||||
import java.io.{IOException, InputStream, OutputStream}
|
||||
import java.io._
|
||||
import java.lang.System.currentTimeMillis
|
||||
import java.util.concurrent.Semaphore
|
||||
|
||||
import android.util.Log
|
||||
|
||||
import scala.collection.JavaConversions._
|
||||
import scala.collection.immutable.Stream
|
||||
import scala.util.control.Exception._
|
||||
|
||||
class StreamLogger(is: InputStream, tag: String) extends Thread {
|
||||
|
||||
def withCloseable[T <: Closeable, R](t: T)(f: T => R): R = {
|
||||
allCatch.andFinally{t.close} apply { f(t) }
|
||||
}
|
||||
|
||||
override def run() {
|
||||
withCloseable(new BufferedReader(new InputStreamReader(is))) {
|
||||
br => try Stream.continually(br.readLine()).takeWhile(_ != null).foreach(Log.i(tag, _)) catch {
|
||||
case ignore: IOException =>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @author ayanamist@gmail.com
|
||||
*/
|
||||
class GuardedProcess(cmd: Seq[String]) extends Process {
|
||||
class GuardedProcess(cmd: Seq[String]) {
|
||||
private val TAG = classOf[GuardedProcess].getSimpleName
|
||||
|
||||
@volatile private var guardThread: Thread = _
|
||||
@@ -72,6 +89,9 @@ class GuardedProcess(cmd: Seq[String]) extends Process {
|
||||
|
||||
process = new ProcessBuilder(cmd).redirectErrorStream(true).start
|
||||
|
||||
val is = process.getInputStream
|
||||
new StreamLogger(is, TAG).start
|
||||
|
||||
if (callback == null) callback = onRestartCallback else callback()
|
||||
|
||||
semaphore.release
|
||||
@@ -118,11 +138,6 @@ class GuardedProcess(cmd: Seq[String]) extends Process {
|
||||
process.destroy()
|
||||
}
|
||||
|
||||
def exitValue: Int = throw new UnsupportedOperationException
|
||||
def getErrorStream: InputStream = throw new UnsupportedOperationException
|
||||
def getInputStream: InputStream = throw new UnsupportedOperationException
|
||||
def getOutputStream: OutputStream = throw new UnsupportedOperationException
|
||||
|
||||
@throws(classOf[InterruptedException])
|
||||
def waitFor = {
|
||||
guardThread.join()
|
||||
|
||||
@@ -114,12 +114,14 @@ final class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClic
|
||||
finish
|
||||
}
|
||||
|
||||
def onKey(v: View, keyCode: Int, event: KeyEvent) = if (event.getAction == KeyEvent.ACTION_UP) keyCode match {
|
||||
def onKey(v: View, keyCode: Int, event: KeyEvent) = if (event.getAction == KeyEvent.ACTION_DOWN) keyCode match {
|
||||
case KeyEvent.KEYCODE_DPAD_LEFT =>
|
||||
val index = getAdapterPosition
|
||||
profilesAdapter.remove(index)
|
||||
undoManager.remove(index, item)
|
||||
true
|
||||
if (index >= 0) {
|
||||
profilesAdapter.remove(index)
|
||||
undoManager.remove(index, item)
|
||||
true
|
||||
} else false
|
||||
case _ => false
|
||||
} else false
|
||||
}
|
||||
|
||||
@@ -78,6 +78,7 @@ object Typefaces {
|
||||
} catch {
|
||||
case e: Exception =>
|
||||
Log.e(TAG, "Could not get typeface '" + assetPath + "' because " + e.getMessage)
|
||||
app.track(e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -185,8 +186,8 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext {
|
||||
snackbar.show
|
||||
}
|
||||
|
||||
if (!app.settings.getBoolean(app.getVersionName, false)) {
|
||||
app.editor.putBoolean(app.getVersionName, true).apply()
|
||||
if (!app.settings.getBoolean(BuildConfig.VERSION_NAME, false)) {
|
||||
app.editor.putBoolean(BuildConfig.VERSION_NAME, true).apply()
|
||||
|
||||
recovery()
|
||||
|
||||
@@ -246,6 +247,7 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext {
|
||||
} catch {
|
||||
case e: IOException =>
|
||||
Log.e(TAG, e.getMessage)
|
||||
app.track(e)
|
||||
}
|
||||
if (files != null) {
|
||||
for (file <- files) {
|
||||
@@ -267,6 +269,7 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext {
|
||||
} catch {
|
||||
case ex: Exception =>
|
||||
Log.e(TAG, ex.getMessage)
|
||||
app.track(ex)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -500,11 +503,8 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext {
|
||||
clearDialog()
|
||||
}
|
||||
|
||||
private var _isDestroyed: Boolean = _
|
||||
override def isDestroyed = if (Build.VERSION.SDK_INT >= 17) super.isDestroyed else _isDestroyed
|
||||
override def onDestroy() {
|
||||
super.onDestroy()
|
||||
_isDestroyed = true
|
||||
detachService()
|
||||
new BackupManager(this).dataChanged()
|
||||
handler.removeCallbacksAndMessages(null)
|
||||
|
||||
@@ -43,12 +43,11 @@ import java.util
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
import android.app.Application
|
||||
import android.content.pm.PackageManager
|
||||
import android.preference.PreferenceManager
|
||||
import android.support.v7.app.AppCompatDelegate
|
||||
import com.github.shadowsocks.database.{DBHelper, ProfileManager}
|
||||
import com.github.shadowsocks.utils.{Key, Utils}
|
||||
import com.google.android.gms.analytics.{GoogleAnalytics, HitBuilders}
|
||||
import com.github.shadowsocks.utils.{Key, Utils, TcpFastOpen}
|
||||
import com.google.android.gms.analytics.{GoogleAnalytics, HitBuilders, StandardExceptionParser}
|
||||
import com.google.android.gms.common.api.ResultCallback
|
||||
import com.google.android.gms.tagmanager.{ContainerHolder, TagManager}
|
||||
import com.j256.ormlite.logger.LocalLog
|
||||
@@ -70,17 +69,14 @@ class ShadowsocksApplication extends Application {
|
||||
def isNatEnabled = settings.getBoolean(Key.isNAT, false)
|
||||
def isVpnEnabled = !isNatEnabled
|
||||
|
||||
def getVersionName = try {
|
||||
getPackageManager.getPackageInfo(getPackageName, 0).versionName
|
||||
} catch {
|
||||
case _: PackageManager.NameNotFoundException => "Package name not found"
|
||||
case _: Throwable => null
|
||||
}
|
||||
|
||||
// send event
|
||||
def track(category: String, action: String) = tracker.send(new HitBuilders.EventBuilder()
|
||||
.setAction(action)
|
||||
.setLabel(getVersionName)
|
||||
.setLabel(BuildConfig.VERSION_NAME)
|
||||
.build())
|
||||
def track(t: Throwable) = tracker.send(new HitBuilders.ExceptionBuilder()
|
||||
.setDescription(new StandardExceptionParser(this, null).getDescription(Thread.currentThread.getName, t))
|
||||
.setFatal(false)
|
||||
.build())
|
||||
|
||||
def profileId = settings.getInt(Key.id, -1)
|
||||
@@ -115,6 +111,8 @@ class ShadowsocksApplication extends Application {
|
||||
}
|
||||
}
|
||||
pending.setResultCallback(callback, 2, TimeUnit.SECONDS)
|
||||
|
||||
TcpFastOpen.enabled(settings.getBoolean(Key.tfo, false))
|
||||
}
|
||||
|
||||
def refreshContainerHolder {
|
||||
|
||||
@@ -40,7 +40,6 @@
|
||||
package com.github.shadowsocks
|
||||
|
||||
import java.io.File
|
||||
import java.lang.Process
|
||||
import java.net.{Inet6Address, InetAddress}
|
||||
import java.util.Locale
|
||||
|
||||
@@ -65,10 +64,10 @@ class ShadowsocksNatService extends BaseService {
|
||||
private var notification: ShadowsocksNotification = _
|
||||
val myUid = android.os.Process.myUid()
|
||||
|
||||
var sslocalProcess: Process = _
|
||||
var sstunnelProcess: Process = _
|
||||
var redsocksProcess: Process = _
|
||||
var pdnsdProcess: Process = _
|
||||
var sslocalProcess: GuardedProcess = _
|
||||
var sstunnelProcess: GuardedProcess = _
|
||||
var redsocksProcess: GuardedProcess = _
|
||||
var pdnsdProcess: GuardedProcess = _
|
||||
var su: Shell.Interactive = _
|
||||
|
||||
def startShadowsocksDaemon() {
|
||||
@@ -97,8 +96,7 @@ class ShadowsocksNatService extends BaseService {
|
||||
p.println(conf)
|
||||
})
|
||||
|
||||
val cmd = new ArrayBuffer[String]
|
||||
cmd += (getApplicationInfo.dataDir + "/ss-local"
|
||||
val cmd = ArrayBuffer[String](getApplicationInfo.dataDir + "/ss-local"
|
||||
, "-b" , "127.0.0.1"
|
||||
, "-t" , "600"
|
||||
, "-P", getApplicationInfo.dataDir
|
||||
@@ -106,6 +104,8 @@ class ShadowsocksNatService extends BaseService {
|
||||
|
||||
if (profile.auth) cmd += "-A"
|
||||
|
||||
if (TcpFastOpen.sendEnabled) cmd += "--fast-open"
|
||||
|
||||
if (profile.route != Route.ALL) {
|
||||
cmd += "--acl"
|
||||
cmd += (getApplicationInfo.dataDir + "/acl.list")
|
||||
@@ -118,15 +118,16 @@ class ShadowsocksNatService extends BaseService {
|
||||
def startKcptunDaemon() {
|
||||
if (profile.kcpcli == null) profile.kcpcli = ""
|
||||
|
||||
val cmd = new ArrayBuffer[String]
|
||||
cmd += (getApplicationInfo.dataDir + "/kcptun"
|
||||
val cmd = ArrayBuffer[String](getApplicationInfo.dataDir + "/kcptun"
|
||||
, "-r", profile.host + ":" + profile.kcpPort
|
||||
, "-l", "127.0.0.1:" + (profile.localPort + 90)
|
||||
, profile.kcpcli.trim.replaceAll("[\"']", ""))
|
||||
, "-l", "127.0.0.1:" + (profile.localPort + 90))
|
||||
try cmd ++= Utils.translateCommandline(profile.kcpcli) catch {
|
||||
case exc: Exception => throw KcpcliParseException(exc)
|
||||
}
|
||||
|
||||
if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" "))
|
||||
|
||||
kcptunProcess = new GuardedProcess(cmd.mkString(" ").split(" ").toSeq).start()
|
||||
kcptunProcess = new GuardedProcess(cmd).start()
|
||||
}
|
||||
|
||||
def startDNSTunnel() {
|
||||
@@ -137,8 +138,7 @@ class ShadowsocksNatService extends BaseService {
|
||||
Utils.printToFile(new File(getApplicationInfo.dataDir + "/ss-tunnel-nat.conf"))(p => {
|
||||
p.println(conf)
|
||||
})
|
||||
val cmd = new ArrayBuffer[String]
|
||||
cmd += (getApplicationInfo.dataDir + "/ss-tunnel"
|
||||
val cmd = ArrayBuffer[String](getApplicationInfo.dataDir + "/ss-tunnel"
|
||||
, "-u"
|
||||
, "-t" , "10"
|
||||
, "-b" , "127.0.0.1"
|
||||
@@ -166,8 +166,7 @@ class ShadowsocksNatService extends BaseService {
|
||||
Utils.printToFile(new File(getApplicationInfo.dataDir + "/ss-tunnel-nat.conf"))(p => {
|
||||
p.println(conf)
|
||||
})
|
||||
val cmdBuf = new ArrayBuffer[String]
|
||||
cmdBuf += (getApplicationInfo.dataDir + "/ss-tunnel"
|
||||
val cmdBuf = ArrayBuffer[String](getApplicationInfo.dataDir + "/ss-tunnel"
|
||||
, "-t" , "10"
|
||||
, "-b" , "127.0.0.1"
|
||||
, "-l" , (profile.localPort + 63).toString
|
||||
@@ -196,27 +195,26 @@ class ShadowsocksNatService extends BaseService {
|
||||
Utils.printToFile(new File(getApplicationInfo.dataDir + "/pdnsd-nat.conf"))(p => {
|
||||
p.println(conf)
|
||||
})
|
||||
val cmd = getApplicationInfo.dataDir + "/pdnsd -c " + getApplicationInfo.dataDir + "/pdnsd-nat.conf"
|
||||
val cmd = Array(getApplicationInfo.dataDir + "/pdnsd", "-c", getApplicationInfo.dataDir + "/pdnsd-nat.conf")
|
||||
|
||||
if (BuildConfig.DEBUG) Log.d(TAG, cmd)
|
||||
if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" "))
|
||||
|
||||
pdnsdProcess = new GuardedProcess(cmd.split(" ").toSeq).start()
|
||||
pdnsdProcess = new GuardedProcess(cmd).start()
|
||||
}
|
||||
|
||||
def startRedsocksDaemon() {
|
||||
val conf = ConfigUtils.REDSOCKS.formatLocal(Locale.ENGLISH, profile.localPort)
|
||||
val cmd = "%s/redsocks -c %s/redsocks-nat.conf"
|
||||
.formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir, getApplicationInfo.dataDir)
|
||||
val cmd = Array(getApplicationInfo.dataDir + "/redsocks", "-c", getApplicationInfo.dataDir + "/redsocks-nat.conf")
|
||||
Utils.printToFile(new File(getApplicationInfo.dataDir + "/redsocks-nat.conf"))(p => {
|
||||
p.println(conf)
|
||||
})
|
||||
|
||||
if (BuildConfig.DEBUG) Log.d(TAG, cmd)
|
||||
redsocksProcess = new GuardedProcess(cmd.split(" ").toSeq).start()
|
||||
if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" "))
|
||||
redsocksProcess = new GuardedProcess(cmd).start()
|
||||
}
|
||||
|
||||
/** Called when the activity is first created. */
|
||||
def handleConnection {
|
||||
def handleConnection() {
|
||||
|
||||
startDNSTunnel()
|
||||
startRedsocksDaemon()
|
||||
@@ -303,9 +301,11 @@ class ShadowsocksNatService extends BaseService {
|
||||
override def startRunner(profile: Profile) = if (su == null) {
|
||||
su = new Shell.Builder().useSU().setWantSTDERR(true).setWatchdogTimeout(10).open((_, exitCode, _) =>
|
||||
if (exitCode == 0) super.startRunner(profile) else {
|
||||
Log.wtf(TAG, "libsuperuser#55 has been fixed. Please remove the redundant code.")
|
||||
su.close()
|
||||
su = null
|
||||
if (su != null) {
|
||||
Log.wtf(TAG, "libsuperuser#55 has been fixed. Please remove the redundant code.")
|
||||
su.close()
|
||||
su = null
|
||||
}
|
||||
super.stopRunner(true, getString(R.string.nat_no_root))
|
||||
})
|
||||
su.waitForIdle()
|
||||
@@ -319,31 +319,19 @@ class ShadowsocksNatService extends BaseService {
|
||||
override def connect() {
|
||||
super.connect()
|
||||
|
||||
if (profile != null) {
|
||||
// Clean up
|
||||
killProcesses()
|
||||
|
||||
// Clean up
|
||||
killProcesses()
|
||||
|
||||
var resolved = false
|
||||
if (!Utils.isNumeric(profile.host)) {
|
||||
Utils.resolve(profile.host, enableIPv6 = true) match {
|
||||
case Some(a) =>
|
||||
profile.host = a
|
||||
resolved = true
|
||||
case None => resolved = false
|
||||
}
|
||||
} else {
|
||||
resolved = true
|
||||
}
|
||||
|
||||
if (!resolved) stopRunner(true, getString(R.string.invalid_server)) else {
|
||||
handleConnection
|
||||
// Set DNS
|
||||
su.addCommand(Utils.FLUSH_DNS)
|
||||
changeState(State.CONNECTED)
|
||||
notification = new ShadowsocksNotification(this, profile.name, true)
|
||||
}
|
||||
if (!Utils.isNumeric(profile.host)) Utils.resolve(profile.host, enableIPv6 = true) match {
|
||||
case Some(a) => profile.host = a
|
||||
case None => throw NameNotResolvedException()
|
||||
}
|
||||
|
||||
handleConnection()
|
||||
// Set DNS
|
||||
su.addCommand(Utils.FLUSH_DNS)
|
||||
changeState(State.CONNECTED)
|
||||
notification = new ShadowsocksNotification(this, profile.name, true)
|
||||
}
|
||||
|
||||
override def stopRunner(stopService: Boolean, msg: String = null) {
|
||||
|
||||
@@ -49,7 +49,6 @@ class ShadowsocksNotification(private val service: BaseService, profileName: Str
|
||||
case _ =>
|
||||
}
|
||||
private lazy val style = new BigTextStyle(builder)
|
||||
private val showOnUnlock = visible && Utils.isLollipopOrAbove
|
||||
private var isVisible = true
|
||||
update(if (service.getSystemService(Context.POWER_SERVICE).asInstanceOf[PowerManager].isScreenOn)
|
||||
Intent.ACTION_SCREEN_ON else Intent.ACTION_SCREEN_OFF, true)
|
||||
@@ -57,19 +56,19 @@ class ShadowsocksNotification(private val service: BaseService, profileName: Str
|
||||
val screenFilter = new IntentFilter()
|
||||
screenFilter.addAction(Intent.ACTION_SCREEN_ON)
|
||||
screenFilter.addAction(Intent.ACTION_SCREEN_OFF)
|
||||
if (showOnUnlock) screenFilter.addAction(Intent.ACTION_USER_PRESENT)
|
||||
if (visible && Utils.isLollipopOrAbove) screenFilter.addAction(Intent.ACTION_USER_PRESENT)
|
||||
service.registerReceiver(lockReceiver, screenFilter)
|
||||
|
||||
private def update(action: String, forceShow: Boolean = false) =
|
||||
if (forceShow || service.getState == State.CONNECTED) action match {
|
||||
case Intent.ACTION_SCREEN_OFF =>
|
||||
setVisible(false, forceShow)
|
||||
setVisible(visible && !Utils.isLollipopOrAbove, forceShow)
|
||||
unregisterCallback // unregister callback to save battery
|
||||
case Intent.ACTION_SCREEN_ON =>
|
||||
setVisible(showOnUnlock && !keyGuard.inKeyguardRestrictedInputMode, forceShow)
|
||||
setVisible(visible && Utils.isLollipopOrAbove && !keyGuard.inKeyguardRestrictedInputMode, forceShow)
|
||||
service.binder.registerCallback(callback)
|
||||
callbackRegistered = true
|
||||
case Intent.ACTION_USER_PRESENT => setVisible(showOnUnlock, forceShow)
|
||||
case Intent.ACTION_USER_PRESENT => setVisible(true, forceShow)
|
||||
}
|
||||
|
||||
private def unregisterCallback = if (callbackRegistered) {
|
||||
|
||||
@@ -68,7 +68,7 @@ class ShadowsocksQuickSwitchActivity extends AppCompatActivity {
|
||||
profilesList.setItemAnimator(new DefaultItemAnimator)
|
||||
profilesList.setAdapter(profilesAdapter)
|
||||
if (app.profileId >= 0) lm.scrollToPosition(profilesAdapter.profiles.zipWithIndex.collectFirst {
|
||||
case (profile, i) if profile.id == app.profileId => i + 1
|
||||
case (profile, i) if profile.id == app.profileId => i
|
||||
}.getOrElse(0))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,14 +5,15 @@ import java.util.Locale
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
|
||||
import android.content.{Intent, SharedPreferences}
|
||||
import android.net.Uri
|
||||
import android.os.{Build, Bundle}
|
||||
import android.os.Bundle
|
||||
import android.preference.{Preference, PreferenceFragment, SwitchPreference}
|
||||
import android.support.design.widget.Snackbar
|
||||
import android.support.v7.app.AlertDialog
|
||||
import android.webkit.{WebView, WebViewClient}
|
||||
import com.github.shadowsocks.ShadowsocksApplication.app
|
||||
import com.github.shadowsocks.database.Profile
|
||||
import com.github.shadowsocks.preferences._
|
||||
import com.github.shadowsocks.utils.{Key, Utils}
|
||||
import com.github.shadowsocks.utils.{Key, TcpFastOpen, Utils}
|
||||
|
||||
object ShadowsocksSettings {
|
||||
// Constants
|
||||
@@ -154,15 +155,27 @@ class ShadowsocksSettings extends PreferenceFragment with OnSharedPreferenceChan
|
||||
}
|
||||
switch.setChecked(BootReceiver.getEnabled(activity))
|
||||
|
||||
val tfo = findPreference(Key.tfo).asInstanceOf[SwitchPreference]
|
||||
tfo.setChecked(TcpFastOpen.sendEnabled)
|
||||
tfo.setOnPreferenceChangeListener((_, v) => {
|
||||
val value = v.asInstanceOf[Boolean]
|
||||
val result = TcpFastOpen.enabled(value)
|
||||
if (result != null && result != "Success.")
|
||||
Snackbar.make(activity.findViewById(android.R.id.content), result, Snackbar.LENGTH_LONG).show()
|
||||
value == TcpFastOpen.sendEnabled
|
||||
})
|
||||
if (!TcpFastOpen.supported) {
|
||||
tfo.setEnabled(false)
|
||||
tfo.setSummary(getString(R.string.tcp_fastopen_summary_unsupported, java.lang.System.getProperty("os.version")))
|
||||
}
|
||||
|
||||
findPreference("recovery").setOnPreferenceClickListener((preference: Preference) => {
|
||||
app.track(TAG, "reset")
|
||||
activity.recovery()
|
||||
true
|
||||
})
|
||||
|
||||
val flush = findPreference("flush_dnscache")
|
||||
if (Build.VERSION.SDK_INT < 17) flush.setSummary(R.string.flush_dnscache_summary)
|
||||
flush.setOnPreferenceClickListener(_ => {
|
||||
findPreference("flush_dnscache").setOnPreferenceClickListener(_ => {
|
||||
app.track(TAG, "flush_dnscache")
|
||||
activity.flushDnsCache()
|
||||
true
|
||||
@@ -184,7 +197,7 @@ class ShadowsocksSettings extends PreferenceFragment with OnSharedPreferenceChan
|
||||
})
|
||||
|
||||
new AlertDialog.Builder(activity)
|
||||
.setTitle(getString(R.string.about_title).formatLocal(Locale.ENGLISH, app.getVersionName))
|
||||
.setTitle(getString(R.string.about_title).formatLocal(Locale.ENGLISH, BuildConfig.VERSION_NAME))
|
||||
.setNegativeButton(getString(android.R.string.ok), null)
|
||||
.setView(web)
|
||||
.create()
|
||||
|
||||
@@ -40,7 +40,6 @@
|
||||
package com.github.shadowsocks
|
||||
|
||||
import java.io.File
|
||||
import java.lang.Process
|
||||
import java.util.Locale
|
||||
|
||||
import android.content._
|
||||
@@ -48,9 +47,9 @@ import android.content.pm.PackageManager.NameNotFoundException
|
||||
import android.net.VpnService
|
||||
import android.os._
|
||||
import android.util.Log
|
||||
import com.github.shadowsocks.utils._
|
||||
import com.github.shadowsocks.ShadowsocksApplication.app
|
||||
import com.github.shadowsocks.database.Profile
|
||||
import com.github.shadowsocks.utils._
|
||||
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
|
||||
@@ -63,10 +62,10 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
||||
var vpnThread: ShadowsocksVpnThread = _
|
||||
private var notification: ShadowsocksNotification = _
|
||||
|
||||
var sslocalProcess: Process = _
|
||||
var sstunnelProcess: Process = _
|
||||
var pdnsdProcess: Process = _
|
||||
var tun2socksProcess: Process = _
|
||||
var sslocalProcess: GuardedProcess = _
|
||||
var sstunnelProcess: GuardedProcess = _
|
||||
var pdnsdProcess: GuardedProcess = _
|
||||
var tun2socksProcess: GuardedProcess = _
|
||||
|
||||
override def onBind(intent: Intent): IBinder = {
|
||||
val action = intent.getAction
|
||||
@@ -148,42 +147,28 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
||||
override def connect() = {
|
||||
super.connect()
|
||||
|
||||
if (profile != null) {
|
||||
vpnThread = new ShadowsocksVpnThread(this)
|
||||
vpnThread.start()
|
||||
|
||||
vpnThread = new ShadowsocksVpnThread(this)
|
||||
vpnThread.start()
|
||||
// reset the context
|
||||
killProcesses()
|
||||
|
||||
// reset the context
|
||||
killProcesses()
|
||||
|
||||
// Resolve the server address
|
||||
var resolved: Boolean = false
|
||||
if (!Utils.isNumeric(profile.host)) {
|
||||
Utils.resolve(profile.host, enableIPv6 = true) match {
|
||||
case Some(addr) =>
|
||||
profile.host = addr
|
||||
resolved = true
|
||||
case None => resolved = false
|
||||
}
|
||||
} else {
|
||||
resolved = true
|
||||
}
|
||||
|
||||
if (!resolved) stopRunner(true, getString(R.string.invalid_server)) else if (handleConnection) {
|
||||
changeState(State.CONNECTED)
|
||||
notification = new ShadowsocksNotification(this, profile.name)
|
||||
} else {
|
||||
changeState(State.STOPPED, getString(R.string.service_failed))
|
||||
stopRunner(true)
|
||||
}
|
||||
// Resolve the server address
|
||||
if (!Utils.isNumeric(profile.host)) Utils.resolve(profile.host, enableIPv6 = true) match {
|
||||
case Some(addr) => profile.host = addr
|
||||
case None => throw NameNotResolvedException()
|
||||
}
|
||||
|
||||
handleConnection()
|
||||
changeState(State.CONNECTED)
|
||||
notification = new ShadowsocksNotification(this, profile.name)
|
||||
}
|
||||
|
||||
/** Called when the activity is first created. */
|
||||
def handleConnection: Boolean = {
|
||||
def handleConnection() {
|
||||
|
||||
val fd = startVpn()
|
||||
if (!sendFd(fd)) return false
|
||||
if (!sendFd(fd)) throw new Exception("sendFd failed")
|
||||
|
||||
if (profile.kcp) {
|
||||
startKcptunDaemon()
|
||||
@@ -199,24 +184,23 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
||||
startDnsDaemon()
|
||||
startDnsTunnel()
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
def startKcptunDaemon() {
|
||||
if (profile.kcpcli == null) profile.kcpcli = ""
|
||||
|
||||
val cmd = new ArrayBuffer[String]
|
||||
cmd += (getApplicationInfo.dataDir + "/kcptun"
|
||||
val cmd = ArrayBuffer(getApplicationInfo.dataDir + "/kcptun"
|
||||
, "-r", profile.host + ":" + profile.kcpPort
|
||||
, "-l", "127.0.0.1:" + (profile.localPort + 90)
|
||||
, "--path", getApplicationInfo.dataDir + "/protect_path"
|
||||
, profile.kcpcli.trim.replaceAll("[\"']", ""))
|
||||
, "--path", getApplicationInfo.dataDir + "/protect_path")
|
||||
try cmd ++= Utils.translateCommandline(profile.kcpcli) catch {
|
||||
case exc: Exception => throw KcpcliParseException(exc)
|
||||
}
|
||||
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.d(TAG, cmd.mkString(" "))
|
||||
|
||||
kcptunProcess = new GuardedProcess(cmd.mkString(" ").split(" ").toSeq).start()
|
||||
kcptunProcess = new GuardedProcess(cmd).start()
|
||||
}
|
||||
|
||||
def startShadowsocksUDPDaemon() {
|
||||
@@ -227,8 +211,7 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
||||
p.println(conf)
|
||||
})
|
||||
|
||||
val cmd = new ArrayBuffer[String]
|
||||
cmd += (getApplicationInfo.dataDir + "/ss-local", "-V", "-U"
|
||||
val cmd = ArrayBuffer[String](getApplicationInfo.dataDir + "/ss-local", "-V", "-U"
|
||||
, "-b", "127.0.0.1"
|
||||
, "-t", "600"
|
||||
, "-P", getApplicationInfo.dataDir
|
||||
@@ -267,8 +250,7 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
||||
p.println(conf)
|
||||
})
|
||||
|
||||
val cmd = new ArrayBuffer[String]
|
||||
cmd += (getApplicationInfo.dataDir + "/ss-local", "-V"
|
||||
val cmd = ArrayBuffer[String](getApplicationInfo.dataDir + "/ss-local", "-V"
|
||||
, "-b", "127.0.0.1"
|
||||
, "-t", "600"
|
||||
, "-P", getApplicationInfo.dataDir
|
||||
@@ -283,6 +265,8 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
||||
cmd += (getApplicationInfo.dataDir + "/acl.list")
|
||||
}
|
||||
|
||||
if (TcpFastOpen.sendEnabled) cmd += "--fast-open"
|
||||
|
||||
if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" "))
|
||||
|
||||
sslocalProcess = new GuardedProcess(cmd).start()
|
||||
@@ -301,8 +285,7 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
||||
Utils.printToFile(new File(getApplicationInfo.dataDir + "/ss-tunnel-vpn.conf"))(p => {
|
||||
p.println(conf)
|
||||
})
|
||||
val cmd = new ArrayBuffer[String]
|
||||
cmd += (getApplicationInfo.dataDir + "/ss-tunnel"
|
||||
val cmd = ArrayBuffer[String](getApplicationInfo.dataDir + "/ss-tunnel"
|
||||
, "-V"
|
||||
, "-t", "10"
|
||||
, "-b", "127.0.0.1"
|
||||
@@ -331,11 +314,11 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
||||
Utils.printToFile(new File(getApplicationInfo.dataDir + "/pdnsd-vpn.conf"))(p => {
|
||||
p.println(conf)
|
||||
})
|
||||
val cmd = getApplicationInfo.dataDir + "/pdnsd -c " + getApplicationInfo.dataDir + "/pdnsd-vpn.conf"
|
||||
val cmd = Array(getApplicationInfo.dataDir + "/pdnsd", "-c", getApplicationInfo.dataDir + "/pdnsd-vpn.conf")
|
||||
|
||||
if (BuildConfig.DEBUG) Log.d(TAG, cmd)
|
||||
if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" "))
|
||||
|
||||
pdnsdProcess = new GuardedProcess(cmd.split(" ").toSeq).start()
|
||||
pdnsdProcess = new GuardedProcess(cmd).start()
|
||||
}
|
||||
|
||||
def startVpn(): Int = {
|
||||
@@ -364,7 +347,7 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
||||
}
|
||||
} catch {
|
||||
case ex: NameNotFoundException =>
|
||||
Log.e(TAG, "Invalid package name", ex);
|
||||
Log.e(TAG, "Invalid package name", ex)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -386,45 +369,36 @@ class ShadowsocksVpnService extends VpnService with BaseService {
|
||||
conn = builder.establish()
|
||||
if (conn == null) changeState(State.STOPPED, getString(R.string.reboot_required))
|
||||
} catch {
|
||||
case ex: IllegalStateException =>
|
||||
changeState(State.STOPPED, ex.getMessage)
|
||||
conn = null
|
||||
case ex: Exception =>
|
||||
ex.printStackTrace()
|
||||
conn = null
|
||||
}
|
||||
|
||||
if (conn == null) {
|
||||
stopRunner(true)
|
||||
return -1
|
||||
app.track(ex)
|
||||
stopRunner(true, ex.getMessage)
|
||||
return -1
|
||||
}
|
||||
|
||||
val fd = conn.getFd
|
||||
|
||||
var cmd = (getApplicationInfo.dataDir +
|
||||
"/tun2socks --netif-ipaddr %s "
|
||||
+ "--netif-netmask 255.255.255.0 "
|
||||
+ "--socks-server-addr 127.0.0.1:%d "
|
||||
+ "--tunfd %d "
|
||||
+ "--tunmtu %d "
|
||||
+ "--sock-path %s "
|
||||
+ "--loglevel 3")
|
||||
.formatLocal(Locale.ENGLISH,
|
||||
PRIVATE_VLAN.formatLocal(Locale.ENGLISH, "2"),
|
||||
profile.localPort, fd, VPN_MTU, getApplicationInfo.dataDir + "/sock_path")
|
||||
var cmd = ArrayBuffer[String](getApplicationInfo.dataDir + "/tun2socks",
|
||||
"--netif-ipaddr", PRIVATE_VLAN.formatLocal(Locale.ENGLISH, "2"),
|
||||
"--netif-netmask", "255.255.255.0",
|
||||
"--socks-server-addr", "127.0.0.1:" + profile.localPort,
|
||||
"--tunfd", fd.toString,
|
||||
"--tunmtu", VPN_MTU.toString,
|
||||
"--sock-path", getApplicationInfo.dataDir + "/sock_path",
|
||||
"--loglevel", "3")
|
||||
|
||||
if (profile.ipv6)
|
||||
cmd += " --netif-ip6addr " + PRIVATE_VLAN6.formatLocal(Locale.ENGLISH, "2")
|
||||
cmd += ("--netif-ip6addr", PRIVATE_VLAN6.formatLocal(Locale.ENGLISH, "2"))
|
||||
|
||||
if (profile.udpdns)
|
||||
cmd += " --enable-udprelay"
|
||||
cmd += "--enable-udprelay"
|
||||
else
|
||||
cmd += " --dnsgw %s:%d".formatLocal(Locale.ENGLISH, PRIVATE_VLAN.formatLocal(Locale.ENGLISH, "1"),
|
||||
profile.localPort + 53)
|
||||
cmd += ("--dnsgw", "%s:%d".formatLocal(Locale.ENGLISH, PRIVATE_VLAN.formatLocal(Locale.ENGLISH, "1"),
|
||||
profile.localPort + 53))
|
||||
|
||||
if (BuildConfig.DEBUG) Log.d(TAG, cmd)
|
||||
if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" "))
|
||||
|
||||
tun2socksProcess = new GuardedProcess(cmd.split(" ").toSeq).start(() => sendFd(fd))
|
||||
tun2socksProcess = new GuardedProcess(cmd).start(() => sendFd(fd))
|
||||
|
||||
fd
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ import java.util.concurrent.Executors
|
||||
|
||||
import android.net.{LocalServerSocket, LocalSocket, LocalSocketAddress}
|
||||
import android.util.Log
|
||||
import com.github.shadowsocks.ShadowsocksApplication.app
|
||||
|
||||
object ShadowsocksVpnThread {
|
||||
val getInt = classOf[FileDescriptor].getDeclaredMethod("getInt$")
|
||||
@@ -56,7 +57,7 @@ class ShadowsocksVpnThread(vpnService: ShadowsocksVpnService) extends Thread {
|
||||
lazy val PATH = vpnService.getApplicationInfo.dataDir + "/protect_path"
|
||||
|
||||
@volatile var isRunning: Boolean = true
|
||||
@volatile var serverSocket: LocalServerSocket = null
|
||||
@volatile var serverSocket: LocalServerSocket = _
|
||||
|
||||
def closeServerSocket() {
|
||||
if (serverSocket != null) {
|
||||
@@ -76,11 +77,7 @@ class ShadowsocksVpnThread(vpnService: ShadowsocksVpnService) extends Thread {
|
||||
|
||||
override def run() {
|
||||
|
||||
try {
|
||||
new File(PATH).delete()
|
||||
} catch {
|
||||
case _: Exception => // ignore
|
||||
}
|
||||
new File(PATH).delete()
|
||||
|
||||
try {
|
||||
val localSocket = new LocalSocket
|
||||
@@ -89,6 +86,7 @@ class ShadowsocksVpnThread(vpnService: ShadowsocksVpnService) extends Thread {
|
||||
} catch {
|
||||
case e: IOException =>
|
||||
Log.e(TAG, "unable to bind", e)
|
||||
app.track(e)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -127,6 +125,7 @@ class ShadowsocksVpnThread(vpnService: ShadowsocksVpnService) extends Thread {
|
||||
} catch {
|
||||
case e: Exception =>
|
||||
Log.e(TAG, "Error when protect socket", e)
|
||||
app.track(e)
|
||||
}
|
||||
|
||||
// close socket
|
||||
@@ -140,6 +139,7 @@ class ShadowsocksVpnThread(vpnService: ShadowsocksVpnService) extends Thread {
|
||||
} catch {
|
||||
case e: IOException =>
|
||||
Log.e(TAG, "Error when accept socket", e)
|
||||
app.track(e)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ object DBHelper {
|
||||
}
|
||||
|
||||
class DBHelper(val context: Context)
|
||||
extends OrmLiteSqliteOpenHelper(context, DBHelper.PROFILE, null, 19) {
|
||||
extends OrmLiteSqliteOpenHelper(context, DBHelper.PROFILE, null, 20) {
|
||||
import DBHelper._
|
||||
|
||||
lazy val profileDao: Dao[Profile, Int] = getDao(classOf[Profile])
|
||||
@@ -129,7 +129,11 @@ class DBHelper(val context: Context)
|
||||
|
||||
if (oldVersion < 17) {
|
||||
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN kcp SMALLINT;")
|
||||
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN kcpcli VARCHAR DEFAULT '';")
|
||||
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN kcpcli VARCHAR DEFAULT " +
|
||||
"'--crypt none --mode normal --mtu 1200 --nocomp --dscp 46 --parityshard 0';")
|
||||
} else if (oldVersion < 20) {
|
||||
profileDao.executeRawNoArgs("UPDATE `profile` SET kcpcli = '--crypt none --mode normal --mtu 1200 --nocomp " +
|
||||
"--dscp 46 --parityshard 0' WHERE kcpcli IS NULL;")
|
||||
}
|
||||
|
||||
if (oldVersion < 18) {
|
||||
|
||||
@@ -106,7 +106,7 @@ class Profile {
|
||||
var kcpPort: Int = 8399
|
||||
|
||||
@DatabaseField
|
||||
var kcpcli: String = ""
|
||||
var kcpcli: String = "--crypt none --mode normal --mtu 1200 --nocomp --dscp 46 --parityshard 0"
|
||||
|
||||
override def toString = "ss://" + Base64.encodeToString("%s%s:%s@%s:%d".formatLocal(Locale.ENGLISH,
|
||||
method, if (auth) "-auth" else "", password, host, remotePort).getBytes, Base64.NO_PADDING | Base64.NO_WRAP)
|
||||
|
||||
@@ -53,9 +53,9 @@ class ProfileManager(dbHelper: DBHelper) {
|
||||
def setProfileAddedListener(listener: Profile => Any) = this.profileAddedListener = listener
|
||||
|
||||
def createProfile(p: Profile = null): Profile = {
|
||||
val profile = if (p == null) new Profile else p
|
||||
profile.id = 0
|
||||
try {
|
||||
val profile = if (p == null) new Profile else p
|
||||
profile.id = 0
|
||||
app.currentProfile match {
|
||||
case Some(oldProfile) =>
|
||||
// Copy Feature Settings from old profile
|
||||
@@ -72,12 +72,12 @@ class ProfileManager(dbHelper: DBHelper) {
|
||||
if (last != null && last.length == 1 && last(0) != null) profile.userOrder = last(0).toInt + 1
|
||||
dbHelper.profileDao.createOrUpdate(profile)
|
||||
if (profileAddedListener != null) profileAddedListener(profile)
|
||||
profile
|
||||
} catch {
|
||||
case ex: Exception =>
|
||||
Log.e(TAG, "addProfile", ex)
|
||||
p
|
||||
app.track(ex)
|
||||
}
|
||||
profile
|
||||
}
|
||||
|
||||
def updateProfile(profile: Profile): Boolean = {
|
||||
@@ -87,6 +87,7 @@ class ProfileManager(dbHelper: DBHelper) {
|
||||
} catch {
|
||||
case ex: Exception =>
|
||||
Log.e(TAG, "updateProfile", ex)
|
||||
app.track(ex)
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -100,6 +101,7 @@ class ProfileManager(dbHelper: DBHelper) {
|
||||
} catch {
|
||||
case ex: Exception =>
|
||||
Log.e(TAG, "getProfile", ex)
|
||||
app.track(ex)
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -111,6 +113,7 @@ class ProfileManager(dbHelper: DBHelper) {
|
||||
} catch {
|
||||
case ex: Exception =>
|
||||
Log.e(TAG, "delProfile", ex)
|
||||
app.track(ex)
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -122,6 +125,7 @@ class ProfileManager(dbHelper: DBHelper) {
|
||||
} catch {
|
||||
case ex: Exception =>
|
||||
Log.e(TAG, "getAllProfiles", ex)
|
||||
app.track(ex)
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -133,6 +137,7 @@ class ProfileManager(dbHelper: DBHelper) {
|
||||
} catch {
|
||||
case ex: Exception =>
|
||||
Log.e(TAG, "getAllProfiles", ex)
|
||||
app.track(ex)
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.github.shadowsocks.preferences
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.content.{Context, DialogInterface}
|
||||
import android.util.AttributeSet
|
||||
import eu.chainfire.libsuperuser.Shell
|
||||
|
||||
import scala.collection.JavaConversions._
|
||||
|
||||
/**
|
||||
* @author Mygod
|
||||
*/
|
||||
class KcpCliPreference(context: Context, attrs: AttributeSet) extends SummaryEditTextPreference(context, attrs) {
|
||||
override def onPrepareDialogBuilder(builder: AlertDialog.Builder) {
|
||||
super.onPrepareDialogBuilder(builder)
|
||||
builder.setNeutralButton("?", ((_, _) => new AlertDialog.Builder(context)
|
||||
.setTitle("?")
|
||||
.setMessage(asScalaBuffer(Shell.SH.run(context.getApplicationInfo.dataDir + "/kcptun --help"))
|
||||
.dropWhile(line => line != "GLOBAL OPTIONS:")
|
||||
.drop(1)
|
||||
.takeWhile(line => line.length() > 3)
|
||||
.filter(line =>
|
||||
!line.startsWith(" --localaddr ") &&
|
||||
!line.startsWith(" --remoteaddr ") &&
|
||||
!line.startsWith(" --path ") &&
|
||||
!line.startsWith(" --help,") &&
|
||||
!line.startsWith(" --version,"))
|
||||
.mkString("\n")
|
||||
.replaceAll(" {2,}", "\n")
|
||||
.substring(1)) // remove 1st \n
|
||||
.show()): DialogInterface.OnClickListener)
|
||||
}
|
||||
}
|
||||
@@ -4,14 +4,11 @@ package com.github.shadowsocks.utils
|
||||
* @author Mygod
|
||||
*/
|
||||
object CloseUtils {
|
||||
type Closeable = {
|
||||
def close()
|
||||
}
|
||||
type Disconnectable = {
|
||||
def disconnect()
|
||||
}
|
||||
|
||||
def autoClose[A <: Closeable, B](x: => A)(block: A => B): B = {
|
||||
def autoClose[A <: AutoCloseable, B](x: => A)(block: A => B): B = {
|
||||
var a: Option[A] = None
|
||||
try {
|
||||
a = Some(x)
|
||||
|
||||
@@ -171,6 +171,8 @@ object Key {
|
||||
val kcp = "kcp"
|
||||
val kcpPort = "kcpPort"
|
||||
val kcpcli = "kcpcli"
|
||||
|
||||
val tfo = "tcp_fastopen"
|
||||
}
|
||||
|
||||
object State {
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.github.shadowsocks.utils
|
||||
|
||||
import java.io.File
|
||||
|
||||
import eu.chainfire.libsuperuser.Shell
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
|
||||
/**
|
||||
* @author Mygod
|
||||
*/
|
||||
object TcpFastOpen {
|
||||
/**
|
||||
* Is kernel version >= 3.7.1.
|
||||
*/
|
||||
lazy val supported = "^(\\d+)\\.(\\d+)\\.(\\d+)".r.findFirstMatchIn(System.getProperty("os.version")) match {
|
||||
case Some(m) =>
|
||||
val kernel = m.group(1).toInt
|
||||
if (kernel < 3) false else if (kernel > 3) true else {
|
||||
val major = m.group(2).toInt
|
||||
if (major < 7) false else if (major > 7) true else m.group(3).toInt >= 1
|
||||
}
|
||||
case _ => false
|
||||
}
|
||||
|
||||
def sendEnabled = {
|
||||
val file = new File("/proc/sys/net/ipv4/tcp_fastopen")
|
||||
file.canRead && (Utils.readAllLines(file).toInt & 1) > 0
|
||||
}
|
||||
|
||||
def enabled(value: Boolean): String = if (sendEnabled != value) {
|
||||
val res = Shell.run("su", Array(
|
||||
"if echo " + (if (value) 3 else 0) + " > /proc/sys/net/ipv4/tcp_fastopen; then",
|
||||
" echo Success.",
|
||||
"else",
|
||||
" echo Failed.",
|
||||
"fi"), null, true)
|
||||
if (res != null) res.asScala.mkString("\n") else null
|
||||
} else null
|
||||
}
|
||||
@@ -46,13 +46,14 @@ import java.util.concurrent.Executors
|
||||
import android.content.Context
|
||||
import android.net.{LocalServerSocket, LocalSocket, LocalSocketAddress}
|
||||
import android.util.Log
|
||||
import com.github.shadowsocks.ShadowsocksApplication.app
|
||||
|
||||
class TrafficMonitorThread(context: Context) extends Thread {
|
||||
|
||||
val TAG = "TrafficMonitorThread"
|
||||
lazy val PATH = context.getApplicationInfo.dataDir + "/stat_path"
|
||||
|
||||
@volatile var serverSocket: LocalServerSocket = null
|
||||
@volatile var serverSocket: LocalServerSocket = _
|
||||
@volatile var isRunning: Boolean = true
|
||||
|
||||
def closeServerSocket() {
|
||||
@@ -73,11 +74,7 @@ class TrafficMonitorThread(context: Context) extends Thread {
|
||||
|
||||
override def run() {
|
||||
|
||||
try {
|
||||
new File(PATH).delete()
|
||||
} catch {
|
||||
case _: Exception => // ignore
|
||||
}
|
||||
new File(PATH).delete()
|
||||
|
||||
try {
|
||||
val localSocket = new LocalSocket
|
||||
@@ -113,6 +110,7 @@ class TrafficMonitorThread(context: Context) extends Thread {
|
||||
} catch {
|
||||
case e: Exception =>
|
||||
Log.e(TAG, "Error when recv traffic stat", e)
|
||||
app.track(e)
|
||||
}
|
||||
|
||||
// close socket
|
||||
@@ -126,6 +124,7 @@ class TrafficMonitorThread(context: Context) extends Thread {
|
||||
} catch {
|
||||
case e: IOException =>
|
||||
Log.e(TAG, "Error when accept socket", e)
|
||||
app.track(e)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,15 +38,16 @@
|
||||
*/
|
||||
package com.github.shadowsocks.utils
|
||||
|
||||
import java.io.File
|
||||
import java.net._
|
||||
import java.security.MessageDigest
|
||||
import java.util.{Scanner, StringTokenizer}
|
||||
|
||||
import android.animation.{Animator, AnimatorListenerAdapter}
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.{Context, Intent}
|
||||
import android.graphics._
|
||||
import android.os.Build
|
||||
import android.provider.Settings
|
||||
import android.util.{Base64, DisplayMetrics, Log}
|
||||
import android.view.View.MeasureSpec
|
||||
import android.view.{Gravity, View, Window}
|
||||
@@ -56,6 +57,8 @@ import com.github.shadowsocks.{BuildConfig, ShadowsocksRunnerService}
|
||||
import eu.chainfire.libsuperuser.Shell
|
||||
import org.xbill.DNS._
|
||||
|
||||
import scala.collection.JavaConversions._
|
||||
import scala.collection.mutable.ArrayBuffer
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
import scala.concurrent.Future
|
||||
import scala.util.{Failure, Try}
|
||||
@@ -109,6 +112,13 @@ object Utils {
|
||||
})
|
||||
}
|
||||
|
||||
def readAllLines(f: File) = {
|
||||
val scanner = new Scanner(f)
|
||||
try {
|
||||
scanner.useDelimiter("\\Z")
|
||||
scanner.next()
|
||||
} finally scanner.close()
|
||||
}
|
||||
def printToFile(f: java.io.File)(op: java.io.PrintWriter => Unit) {
|
||||
val p = new java.io.PrintWriter(f)
|
||||
try {
|
||||
@@ -118,6 +128,48 @@ object Utils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Crack a command line.
|
||||
* Based on: https://github.com/apache/ant/blob/588ce1f/src/main/org/apache/tools/ant/types/Commandline.java#L471
|
||||
* @param toProcess the command line to process.
|
||||
* @return the command line broken into strings.
|
||||
* An empty or null toProcess parameter results in a zero sized ArrayBuffer.
|
||||
*/
|
||||
def translateCommandline(toProcess: String): ArrayBuffer[String] = {
|
||||
if (toProcess == null || toProcess.length == 0) return ArrayBuffer[String]()
|
||||
val tok = new StringTokenizer(toProcess, "\"' ", true)
|
||||
val result = ArrayBuffer[String]()
|
||||
val current = new StringBuilder()
|
||||
var quote = ' '
|
||||
var last = " "
|
||||
while (tok.hasMoreTokens) {
|
||||
val nextTok = tok.nextToken
|
||||
quote match {
|
||||
case '\'' => nextTok match {
|
||||
case "'" => quote = ' '
|
||||
case _ => current.append(nextTok)
|
||||
}
|
||||
case '"' => nextTok match {
|
||||
case "\"" => quote = ' '
|
||||
case _ => current.append(nextTok)
|
||||
}
|
||||
case _ => nextTok match {
|
||||
case "'" => quote = '\''
|
||||
case "\"" => quote = '"'
|
||||
case " " => if (last != " ") {
|
||||
result.append(current.toString)
|
||||
current.setLength(0)
|
||||
}
|
||||
case _ => current.append(nextTok)
|
||||
}
|
||||
}
|
||||
last = nextTok
|
||||
}
|
||||
if (current.nonEmpty) result.append(current.toString)
|
||||
if (quote == '\'' || quote == '"') throw new Exception("Unbalanced quotes in " + toProcess)
|
||||
result
|
||||
}
|
||||
|
||||
// Because /sys/class/net/* isn't accessible since API level 24
|
||||
final val FLUSH_DNS = "for if in /sys/class/net/*; do " +
|
||||
"if [ \"down\" != $(cat $if/operstate) ]; then " + // up or unknown
|
||||
@@ -128,25 +180,7 @@ object Utils {
|
||||
// Blocked > 3 seconds
|
||||
def toggleAirplaneMode(context: Context) = {
|
||||
val result = Shell.SU.run(FLUSH_DNS)
|
||||
if (result != null && !result.isEmpty) true else if (Build.VERSION.SDK_INT < 17) {
|
||||
toggleBelowApiLevel17(context)
|
||||
true
|
||||
} else false
|
||||
}
|
||||
|
||||
//noinspection ScalaDeprecation
|
||||
private def toggleBelowApiLevel17(context: Context) {
|
||||
// Android 4.2 below
|
||||
Settings.System.putInt(context.getContentResolver, Settings.System.AIRPLANE_MODE_ON, 1)
|
||||
val enableIntent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED)
|
||||
enableIntent.putExtra("state", true)
|
||||
context.sendBroadcast(enableIntent)
|
||||
Thread.sleep(3000)
|
||||
|
||||
Settings.System.putInt(context.getContentResolver, Settings.System.AIRPLANE_MODE_ON, 0)
|
||||
val disableIntent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED)
|
||||
disableIntent.putExtra("state", false)
|
||||
context.sendBroadcast(disableIntent)
|
||||
result != null && !result.isEmpty
|
||||
}
|
||||
|
||||
def resolve(host: String, addrType: Int): Option[String] = {
|
||||
@@ -210,29 +244,22 @@ object Utils {
|
||||
*/
|
||||
def isIPv6Support: Boolean = {
|
||||
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[Inet6Address]) {
|
||||
if (BuildConfig.DEBUG) Log.d(TAG, "IPv6 address detected")
|
||||
return true
|
||||
}
|
||||
for (intf <- enumerationAsScalaIterator(NetworkInterface.getNetworkInterfaces))
|
||||
for (addr <- enumerationAsScalaIterator(intf.getInetAddresses))
|
||||
if (addr.isInstanceOf[Inet6Address] && !addr.isLoopbackAddress && !addr.isLinkLocalAddress) {
|
||||
if (BuildConfig.DEBUG) Log.d(TAG, "IPv6 address detected")
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
case ex: Exception =>
|
||||
Log.e(TAG, "Failed to get interfaces' addresses.", ex)
|
||||
app.track(ex)
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
def startSsService(context: Context) {
|
||||
val isInstalled: Boolean = app.settings.getBoolean(app.getVersionName, false)
|
||||
val isInstalled: Boolean = app.settings.getBoolean(BuildConfig.VERSION_NAME, false)
|
||||
if (!isInstalled) return
|
||||
|
||||
val intent = new Intent(context, classOf[ShadowsocksRunnerService])
|
||||
@@ -245,7 +272,9 @@ object Utils {
|
||||
}
|
||||
|
||||
private val handleFailure: Try[_] => Unit = {
|
||||
case Failure(e) => e.printStackTrace()
|
||||
case Failure(e) =>
|
||||
e.printStackTrace()
|
||||
app.track(e)
|
||||
case _ =>
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user