Compare commits

...
77 Commits
Author SHA1 Message Date
Max Lv 80fdc07bdd Fix #968 2016-11-12 10:24:47 +08:00
Mygod 5ca3a8e9fd Suppress NewApi in lint.xml 2016-11-11 17:58:05 +08:00
Mygod f3a458161a Remove useless components in ads 2016-11-11 11:30:16 +08:00
Mygod e067c6fae2 Update dependency 2016-11-11 11:15:41 +08:00
Mygod f48ad61220 Refine #964 2016-11-04 14:02:44 +08:00
Max Lv 27f9992447 Fix #964 2016-11-04 13:59:06 +08:00
Mygod 546a5d010d Refine design for preference package 2016-11-03 21:20:04 +08:00
Mygod cbb839ec11 Fix up navigation 2016-11-03 20:50:53 +08:00
Mygod 179e5456f8 Refine shortcuts on Android 7.1 2016-11-03 19:18:25 +08:00
Mygod 30700facd3 Remove unused private_route 2016-11-01 13:10:35 +08:00
Max Lv 82103d286d Fix #961 2016-10-31 09:26:13 +08:00
Mygod a9ca3c049d Revert "Revert "Refine Travis rules""
This reverts commit d0b4819dc5.
2016-10-25 20:16:23 +08:00
Mygod 0433704d87 Revert "Force to update Android SDK"
This reverts commit c1dd21e0c3.
2016-10-25 20:16:12 +08:00
Max Lv 2f23462c84 Fix the build 2016-10-25 13:11:38 +08:00
Max Lv c1dd21e0c3 Force to update Android SDK 2016-10-25 12:50:42 +08:00
Max Lv d0b4819dc5 Revert "Refine Travis rules"
This reverts commit 0fdbd19bff.
2016-10-25 12:19:00 +08:00
Mygod d99757f1a3 Fix a deprecation in build.sbt 2016-10-24 16:44:26 +08:00
Mygod 8ed71d1707 Add quick switch to shortcuts 2016-10-24 16:04:34 +08:00
Mygod ffdb6f84e3 Refine shortcuts.xml 2016-10-24 15:49:27 +08:00
Mygod 094b307254 Add taskAffinity to invisible activities 2016-10-24 15:40:57 +08:00
Mygod bd5fff100d Update proguard 2016-10-24 15:27:04 +08:00
Mygod 479e697e6d Experimental support for demo users
TODO: Untested code. Need to find out how to activate demo users.

Fix #950 nevertheless.
2016-10-23 15:39:06 +08:00
Mygod e98dadf0a2 Fix a minor UI issue 2016-10-23 14:35:33 +08:00
Mygod 4cfab40ec2 Fix wrong activity launched when long pressing QS tile 2016-10-23 14:32:57 +08:00
Mygod 0fdbd19bff Refine Travis rules 2016-10-23 14:30:16 +08:00
Mygod 913ec3c06b Fix #949 2016-10-23 14:29:26 +08:00
Mygod 617c6dac1b Fix crashing on Android 6.0+ 2016-10-23 14:22:02 +08:00
Mygod 244c2ecdfd Add quick toggle shortcut (#950)
Mainly for Android 7.1, also backward compatible.
2016-10-23 14:16:56 +08:00
Mygod 54c696d00a Merge branch 'master' of github.com:shadowsocks/shadowsocks-android 2016-10-23 13:13:44 +08:00
Mygod c4d9edc224 Recreate round launcher icon with Android Studio (#950)
The icon was created using shadow.png.
2016-10-23 13:12:32 +08:00
Max Lv 7a7cbf5803 Update travis-ci 2016-10-23 12:07:04 +08:00
Max Lv f53936421d Fix #951 2016-10-23 11:54:39 +08:00
Mygod 5ef2849c4a Update build target to Android 7.1 Developer Preview 2016-10-23 00:30:20 +08:00
Mygod e1f58bfd0c Update dependencies
Disabling `android:extractNativeLib` will reduce apk install size and reduce install time.
2016-10-23 00:24:59 +08:00
Max Lv 7098e57893 Bump version 2016-10-18 15:14:29 +08:00
Max Lv a4c5fc4919 Fix building error 2016-10-18 09:23:44 +08:00
Max Lv 3c69b23a5b Refine ACLs #941 2016-10-15 18:56:47 +08:00
Mygod 738febbac7 Always show stop button for now
Fix #940.
2016-10-15 13:47:29 +08:00
Max Lv ed91010b18 Update shadowsocks-libev 2016-10-14 19:27:33 +08:00
Mygod 8f4d7db797 Remove manual input handling in NumberPickerPreferenceDialogFragment 2016-10-14 00:27:32 +08:00
Mygod 52ead0a02c Fix NullPointerException in NumberPickerPreferenceDialogFragment 2016-10-14 00:18:28 +08:00
Mygod 7d099b1faf Fix IllegalStateException in PreferenceFragment 2016-10-14 00:17:02 +08:00
Mygod 89b9914f15 Fix ETXTBSY in copyAssets 2016-10-14 00:03:30 +08:00
Mygod 809b81a332 Update build tools to 24.0.3 for Travis 2016-10-13 12:23:08 +08:00
Mygod 395c07a0f6 Add last version that doesn't exhibit the issue to issue_template 2016-10-13 12:14:24 +08:00
Mygod cb61c290a4 Refine issue_template 2016-10-13 12:10:40 +08:00
Mygod 9ccf2bc436 Update sbt-android to 1.7.0 2016-10-13 12:08:53 +08:00
Mygod 674d493c2d Refine #937 2016-10-13 10:43:37 +08:00
Max Lv 17382a1002 Bump version 2016-10-13 09:40:15 +08:00
Max Lv 7370b6a0b8 Fix #937 2016-10-13 08:45:10 +08:00
Max Lv 158afb1d9f Bump version 2016-10-12 20:31:46 +08:00
Mygod 085c6dbebf Fix job rescheduling 2016-10-12 18:12:11 +08:00
Mygod c401d7eb3a Extend execution window from 10 seconds to 4 weeks 2016-10-12 15:17:59 +08:00
Mygod 118cea9f7a Add play-services-gcm
This is needed so that Android-Job will use GcmNetworkManager if GCM is installed on user's device. (for KitKat only)
2016-10-12 15:04:01 +08:00
Mygod 5b1ae13faa Clean up extraneous files 2016-10-12 15:03:28 +08:00
Mygod b5f655beb5 Boring bugfix don't look 2016-10-12 14:56:50 +08:00
Mygod 9a3ed94fe0 Refine error handling in services 2016-10-12 14:54:17 +08:00
Mygod 0191d1d0fc Download acl files from GitHub 2016-10-12 14:30:02 +08:00
Mygod 6a56704f52 Refine copyAssets 2016-10-12 11:30:48 +08:00
Mygod a95308d283 Fix a deprecation 2016-10-12 11:26:44 +08:00
Mygod 6acbae0c7e Move copyAssets to BaseService
Fix #926.
2016-10-12 11:24:21 +08:00
Max Lv 97d31cb927 Fix #930 2016-10-10 15:45:25 +08:00
Mygod 7474269f6b Fix crash on 4.4 2016-10-10 13:00:54 +08:00
Mygod 40447ce1d2 Refine fetching ACL from cloud 2016-10-10 09:44:48 +08:00
Max Lv 4162057a22 Add missing files 2016-10-09 15:27:35 +08:00
Max Lv 0f24d7c0bc Add cloud based ACL updating 2016-10-09 10:51:08 +08:00
Mygod bbf614484d Refine unexpected cases for Chinese locales 2016-10-09 00:21:38 +08:00
Mygod 396d31a0a7 Fix Chinese locales once and for all (up to Nougat)
Seriously fix #927. From now on it will work even in Hong Kong (`/^zh-Han[st]-HK$/`) where for some reason people use Simplified Chinese and Traditional Chinese at the same time.
2016-10-09 00:08:06 +08:00
Mygod 812dc6769a Refine pref_all.xml 2016-10-07 23:09:22 +08:00
Max Lv 8feb587ebe Fix #923 2016-10-07 16:54:25 +08:00
Max Lv 89f073cc49 Merge pull request #922 from Mygod/master
Refine GitHub templates
2016-10-06 10:00:36 -05:00
Max Lv 07ac8fb839 Bump version 2016-10-06 22:58:01 +08:00
Mygod 57b75ad971 Refine GitHub templates 2016-10-06 21:59:39 +08:00
Mygod 1d6a0b0f2b Use styling to fix #910 2016-10-05 23:12:25 +08:00
Mygod abcbc98ce3 Refine MyNumberPicker 2016-10-05 22:58:18 +08:00
Mygod 2722b1aa78 Rename package 2016-10-05 22:47:56 +08:00
Mygod 4bfc5ee070 Update email 2016-10-05 13:39:51 +08:00
79 changed files with 20884 additions and 21273 deletions
+31 -5
View File
@@ -1,10 +1,38 @@
Please answer these questions before submitting your issue. Thanks!
Please read FAQ in wiki then answer these questions before submitting your issue. Thanks!
### What version of Shadowsocks are you using?
### Environment
* Android version: *e.g. "7.0.0_r14", more detailed description is preferred*
* Device: *e.g. Google Pixel XL*
* Shadowsocks version: *version code "v3.0.0" or commit ID "a073f85"*
* Last version that did not exhibit the issue (if applicable):
### What Android OS version and device are you using?
### Configuration
_Put an `x` inside the [ ] that applies._
* [ ] IPv4 server address
* [ ] IPv6 server address
* [ ] Client IPv4 availability
* [ ] Client IPv6 availability
* Local port: 1080
* Encrypt method:
* [ ] One-time authentication
* Route
* [ ] All
* [ ] Bypass LAN
* [ ] Bypass China
* [ ] Bypass LAN & China
* [ ] GFW List
* [ ] China List
* [ ] IPv6 route
* [ ] Per-App Proxy
* [ ] Bypass mode
* [ ] UDP Forwarding
* KCP Parameters: (leave blank if not enabled)
* [ ] Auto Connect
* [ ] TCP Fast Open
* [ ] NAT mode
### What did you do?
@@ -14,5 +42,3 @@ Please answer these questions before submitting your issue. Thanks!
### What did you see instead?
### What is your config in detail (with all sensitive info masked)?
+14
View File
@@ -0,0 +1,14 @@
### Type of changes
_Put an `x` inside the [ ] that applies._
* [ ] Bugfix
* [ ] New feature
* [ ] Translation
* [ ] General refinement
* Other:
### Details
Please provide more details about changes if necessary.
+1 -1
View File
@@ -27,7 +27,7 @@ cache:
android:
components:
- tools
- build-tools-24.0.1
- build-tools-25.0.0
- extra-android-m2repository
- extra-google-m2repository
+2 -3
View File
@@ -14,7 +14,7 @@ A [shadowsocks](http://shadowsocks.org) client for Android, written in Scala.
* SBT 0.13.0+
* Go 1.4+
* Android SDK
- Build Tools 24+ (as required by [`sbt-android`](https://github.com/scala-android/sbt-android))
- Build Tools 25+
- Android Support Repository and Google Repository (see `build.sbt` for version)
* Android NDK r12b+
@@ -24,7 +24,6 @@ A [shadowsocks](http://shadowsocks.org) client for Android, written in Scala.
* Set environment variable `ANDROID_NDK_HOME` to `/path/to/android-ndk`
* Set environment variable `GOROOT_BOOTSTRAP` to `/path/to/go`
* 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
@@ -49,7 +48,7 @@ A [shadowsocks](http://shadowsocks.org) client for Android, written in Scala.
### LICENSE
Copyright (C) 2016 by Max Lv <<max.c.lv@gmail.com>>
Copyright (C) 2016 by Mygod Studio <<mygodstudio@gmail.com>>
Copyright (C) 2016 by Mygod Studio <<contact-shadowsocks-android@mygod.be>>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
+41 -50
View File
@@ -1,64 +1,55 @@
import android.Keys._
scalaVersion := "2.11.8"
dexMaxHeap := "4g"
android.Plugin.androidBuild
platformTarget in Android := "android-24"
enablePlugins(AndroidApp)
android.useSupportVectors
name := "shadowsocks"
version := "3.2.7"
versionCode := Some(167)
scalaVersion := "2.11.8"
platformTarget := "android-25"
compileOrder in Compile := CompileOrder.JavaThenScala
compileOrder := CompileOrder.JavaThenScala
javacOptions ++= "-source" :: "1.7" :: "-target" :: "1.7" :: Nil
scalacOptions ++= "-target:jvm-1.7" :: "-Xexperimental" :: Nil
ndkJavah := Seq()
ndkBuild := Seq()
javacOptions ++= Seq("-source", "1.7", "-target", "1.7")
proguardVersion := "5.3.1"
proguardCache := Seq()
proguardOptions ++=
"-keep class com.github.shadowsocks.System { *; }" ::
"-dontwarn com.google.android.gms.internal.**" ::
"-dontwarn com.j256.ormlite.**" ::
"-dontwarn org.xbill.**" ::
Nil
scalacOptions ++= Seq("-target:jvm-1.7", "-Xexperimental")
ndkJavah in Android := List()
ndkBuild in Android := List()
shrinkResources in Android := true
typedResources in Android := false
resConfigs in Android := Seq("ru", "zh", "zh-rCN")
dexMaxHeap in Android := "4g"
shrinkResources := true
typedResources := false
resConfigs := Seq("ru", "zh-rCN", "zh-rTW")
resolvers += Resolver.jcenterRepo
useSupportVectors
libraryDependencies ++= Seq(
"com.android.support" % "design" % "24.2.1",
"com.android.support" % "gridlayout-v7" % "24.2.1",
"com.android.support" % "preference-v14" % "24.2.1",
"com.github.clans" % "fab" % "1.6.4",
"com.github.jorgecastilloprz" % "fabprogresscircle" % "1.01",
"com.github.kevinsawicki" % "http-request" % "6.0",
"com.google.android.gms" % "play-services-ads" % "9.6.1",
"com.google.android.gms" % "play-services-analytics" % "9.6.1",
"com.j256.ormlite" % "ormlite-android" % "5.0",
"com.twofortyfouram" % "android-plugin-api-for-locale" % "1.0.2",
"dnsjava" % "dnsjava" % "2.1.7",
"eu.chainfire" % "libsuperuser" % "1.0.0.201608240809",
"me.dm7.barcodescanner" % "zxing" % "1.9",
"net.glxn.qrgen" % "android" % "2.0"
)
proguardVersion in Android := "5.3"
proguardCache in Android := Seq()
proguardOptions in Android ++= Seq(
"-keep class com.github.shadowsocks.System { *; }",
"-dontwarn com.google.android.gms.internal.**",
"-dontwarn com.j256.ormlite.**",
"-dontwarn org.xbill.**")
libraryDependencies ++=
"com.android.support" % "design" % "25.0.0" ::
"com.android.support" % "gridlayout-v7" % "25.0.0" ::
"com.android.support" % "preference-v14" % "25.0.0" ::
"com.evernote" % "android-job" % "1.1.3" ::
"com.github.clans" % "fab" % "1.6.4" ::
"com.github.jorgecastilloprz" % "fabprogresscircle" % "1.01" ::
"com.github.kevinsawicki" % "http-request" % "6.0" ::
"com.google.android.gms" % "play-services-ads" % "9.8.0" ::
"com.google.android.gms" % "play-services-analytics" % "9.8.0" ::
"com.google.android.gms" % "play-services-gcm" % "9.8.0" ::
"com.j256.ormlite" % "ormlite-android" % "5.0" ::
"com.twofortyfouram" % "android-plugin-api-for-locale" % "1.0.2" ::
"dnsjava" % "dnsjava" % "2.1.7" ::
"eu.chainfire" % "libsuperuser" % "1.0.0.201608240809" ::
"me.dm7.barcodescanner" % "zxing" % "1.9" ::
"net.glxn.qrgen" % "android" % "2.0" ::
Nil
lazy val nativeBuild = TaskKey[Unit]("native-build", "Build native executables")
nativeBuild := {
val logger = streams.value.log
Process("./build.sh") ! logger match {
+3
View File
@@ -0,0 +1,3 @@
<issue id="NewApi">
<ignore path="src/main/res/drawable"/>
</issue>
+1 -1
View File
@@ -1,4 +1,4 @@
addSbtPlugin("org.scala-android" % "sbt-android" % "1.6.18")
addSbtPlugin("org.scala-android" % "sbt-android" % "1.7.1")
addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.1.10")
+39 -12
View File
@@ -1,7 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" package="com.github.shadowsocks"
android:versionCode="163"
android:versionName="3.2.3">
xmlns:tools="http://schemas.android.com/tools" package="com.github.shadowsocks">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
@@ -20,13 +18,15 @@
<uses-sdk
android:minSdkVersion="19"
android:targetSdkVersion="24"/>
android:targetSdkVersion="25"/>
<application
android:allowBackup="true"
android:extractNativeLibs="false"
android:name=".ShadowsocksApplication"
android:hardwareAccelerated="true"
android:icon="@drawable/ic_launcher"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher"
android:backupAgent=".ShadowsocksBackupAgent"
android:label="@string/app_name"
android:theme="@style/Theme.Material"
@@ -37,10 +37,6 @@
<meta-data android:name="com.google.android.backup.api_key"
android:value="AEdPqrEAAAAI_zVxZthz2HDuz9toTvkYvL0L5GA-OjeUIfBeXg"/>
<activity
android:name="com.google.android.gms.ads.AdActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize"/>
<activity
android:name=".Shadowsocks"
android:label="@string/app_name"
@@ -51,14 +47,17 @@
<category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
</intent-filter>
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE" />
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
</intent-filter>
<meta-data android:name="android.app.shortcuts"
android:resource="@xml/shortcuts"/>
</activity>
<activity
android:name=".ShadowsocksRunnerActivity"
android:theme="@android:style/Theme.NoDisplay"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
android:excludeFromRecents="true"
android:taskAffinity=""
android:launchMode="singleTask">
</activity>
@@ -99,13 +98,14 @@
android:launchMode="singleTask"/>
<activity android:name=".TaskerActivity"
android:icon="@drawable/ic_launcher">
android:icon="@mipmap/ic_launcher">
<intent-filter>
<action android:name="com.twofortyfouram.locale.intent.action.EDIT_SETTING" />
</intent-filter>
</activity>
<activity android:name=".ShadowsocksQuickSwitchActivity"
android:label="@string/quick_switch"
android:launchMode="singleInstance"
android:excludeFromRecents="true"
android:taskAffinity=""
@@ -116,6 +116,18 @@
</intent-filter>
</activity>
<activity
android:name=".QuickToggleShortcut"
android:label="@string/quick_toggle"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
android:excludeFromRecents="true"
android:taskAffinity=""
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.CREATE_SHORTCUT" />
</intent-filter>
</activity>
<service
android:name=".ShadowsocksRunnerService"
android:exported="false">
@@ -158,6 +170,21 @@
<action android:name="com.twofortyfouram.locale.intent.action.FIRE_SETTING"/>
</intent-filter>
</receiver>
<!-- android-job components -->
<service android:name="com.evernote.android.job.v21.PlatformJobService"
android:process=":bg"/>
<service android:name="com.evernote.android.job.v14.PlatformAlarmService"
android:process=":bg"/>
<service android:name="com.evernote.android.job.gcm.PlatformGcmService"
android:process=":bg"/>
<receiver android:name="com.evernote.android.job.v14.PlatformAlarmReceiver"
android:process=":bg"/>
<receiver android:name="com.evernote.android.job.JobBootReceiver"
android:process=":bg"/>
<!-- Strip useless components -->
<activity android:name="com.google.android.gms.ads.purchase.InAppPurchaseActivity" tools:node="remove"/>
</application>
</manifest>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -275,7 +275,7 @@ img {
<body>
<p>A <a href="http://shadowsocks.org">Shadowsocks</a> client for Android, written in Scala.</p>
<p>Copyright (C) 2016 by Max Lv <a href="&#x6d;&#97;&#x69;&#108;&#116;&#111;&#x3a;&#x6d;&#97;x&#46;&#99;.&#108;&#118;&#x40;&#103;&#109;&#97;&#x69;&#108;&#46;&#x63;&#x6f;&#109;">&#x6d;&#97;&#x78;.&#x63;&#x2e;&#108;v&#64;&#x67;&#109;&#x61;&#x69;&#108;&#x2e;&#99;&#111;&#109;</a></p>
<p>Copyright (C) 2016 by Mygod Studio <a href="mygodstudio@gmail.com">mygodstudio@gmail.com</a></p>
<p>Copyright (C) 2016 by Mygod Studio <a href="contact-shadowsocks-android@mygod.be">contact-shadowsocks-android@mygod.be</a></p>
<p>This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

+20
View File
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:fillColor="#f5f5f5"
android:pathData="M24,24m-22,0a22,22 0,1 1,44 0a22,22 0,1 1,-44 0Z" />
<path
android:fillColor="#546e7a"
android:pathData="M24,21.8 C25.7673,21.8,27.2,23.2327,27.2,25 C27.2,26.7673,25.7673,28.2,24,28.2
C22.2327,28.2,20.8,26.7673,20.8,25 C20.8,23.2327,22.2327,21.8,24,21.8 Z" />
<path
android:fillColor="#546e7a"
android:pathData="M21,15 L19.17,17 L16,17 A2,2,0,0,0,14,19 L14,31 A2,2,0,0,0,16,33 L32,33
A2,2,0,0,0,34,31 L34,19 A2,2,0,0,0,32,17 L28.83,17 L27,15 Z M24,30
A5,5,0,1,1,29,25 A5,5,0,0,1,24,30 Z" />
</vector>
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:fillColor="#f5f5f5"
android:pathData="M24,24m-22,0a22,22 0,1 1,44 0a22,22 0,1 1,-44 0Z" />
<path
android:fillColor="#546e7a"
android:pathData="M34,14.72 L30.52,30.04 L22.72,27.53 L29.61,19.31 L20.43,27.04 L14,24.62
L34,14.72 Z" />
<path
android:fillColor="#546e7a"
android:pathData="M22.89,29.1 L25.38,29.9 L22.9,33.28 L22.89,29.1 Z" />
</vector>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

@@ -1,4 +1,3 @@
<!--suppress NewApi -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
-1
View File
@@ -1,4 +1,3 @@
<!--suppress NewApi -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
@@ -1,4 +1,3 @@
<!--suppress NewApi -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
@@ -1,4 +1,3 @@
<!--suppress NewApi -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
@@ -1,4 +1,3 @@
<!--suppress NewApi -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
-1
View File
@@ -1,4 +1,3 @@
<!--suppress NewApi -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
-10
View File
@@ -1,10 +0,0 @@
<!--suppress NewApi -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M18,7l-1.41,-1.41 -6.34,6.34 1.41,1.41L18,7zM22.24,5.59L11.66,16.17 7.48,12l-1.41,1.41L11.66,19l12,-12 -1.42,-1.41zM0.41,13.41L6,19l1.41,-1.41L1.83,12 0.41,13.41z"/>
</vector>
@@ -1,4 +1,3 @@
<!--suppress NewApi -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
@@ -1,4 +1,3 @@
<!--suppress NewApi -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
-2
View File
@@ -1,5 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<!--suppress NewApi -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
@@ -1,5 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<!--suppress NewApi -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
-2
View File
@@ -1,5 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<!--suppress NewApi -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
+8 -11
View File
@@ -1,15 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/content_frame"
android:layout_width="match_parent"
android:layout_height="match_parent" />
android:layout_height="match_parent"
android:orientation="vertical">
<include layout="@layout/toolbar_light_dark" />
</FrameLayout>
<me.dm7.barcodescanner.zxing.ZXingScannerView android:id="@+id/scanner"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
</LinearLayout>
+1 -2
View File
@@ -2,10 +2,9 @@
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/action_apply_all"
android:title="@string/action_apply_all"
android:icon="@drawable/ic_done_all"
android:alphabeticShortcut="a"
android:numericShortcut="1"
app:showAsAction="always"/>
app:showAsAction="never"/>
<item android:id="@+id/action_export"
android:title="@string/action_export"
android:icon="@drawable/ic_content_copy"
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

+102 -15
View File
@@ -47,15 +47,6 @@
<item>china-list</item>
</string-array>
<string-array name="private_route">
<item>10.0.0.0/8</item>
<item>127.0.0.0/8</item>
<item>169.254.0.0/16</item>
<item>172.16.0.0/12</item>
<item>192.0.0.0/29</item>
<item>192.0.2.0/24</item>
</string-array>
<string-array name="bypass_private_route">
<item>1.0.0.0/8</item>
<item>2.0.0.0/7</item>
@@ -66,8 +57,47 @@
<item>16.0.0.0/4</item>
<item>32.0.0.0/3</item>
<item>64.0.0.0/3</item>
<item>96.0.0.0/4</item>
<item>112.0.0.0/5</item>
<item>96.0.0.0/6</item>
<item>100.0.0.0/10</item>
<item>100.128.0.0/9</item>
<item>101.0.0.0/8</item>
<item>102.0.0.0/7</item>
<item>104.0.0.0/5</item>
<item>112.0.0.0/10</item>
<item>112.64.0.0/11</item>
<item>112.96.0.0/12</item>
<item>112.112.0.0/13</item>
<item>112.120.0.0/14</item>
<item>112.124.0.0/19</item>
<item>112.124.32.0/21</item>
<item>112.124.40.0/22</item>
<item>112.124.44.0/23</item>
<item>112.124.46.0/24</item>
<item>112.124.48.0/20</item>
<item>112.124.64.0/18</item>
<item>112.124.128.0/17</item>
<item>112.125.0.0/16</item>
<item>112.126.0.0/15</item>
<item>112.128.0.0/9</item>
<item>113.0.0.0/8</item>
<item>114.0.0.0/10</item>
<item>114.64.0.0/11</item>
<item>114.96.0.0/12</item>
<item>114.112.0.0/15</item>
<item>114.114.0.0/18</item>
<item>114.114.64.0/19</item>
<item>114.114.96.0/20</item>
<item>114.114.112.0/23</item>
<item>114.114.115.0/24</item>
<item>114.114.116.0/22</item>
<item>114.114.120.0/21</item>
<item>114.114.128.0/17</item>
<item>114.115.0.0/16</item>
<item>114.116.0.0/14</item>
<item>114.120.0.0/13</item>
<item>114.128.0.0/9</item>
<item>115.0.0.0/8</item>
<item>116.0.0.0/6</item>
<item>120.0.0.0/6</item>
<item>124.0.0.0/7</item>
<item>126.0.0.0/8</item>
@@ -109,12 +139,69 @@
<item>192.8.0.0/13</item>
<item>192.16.0.0/12</item>
<item>192.32.0.0/11</item>
<item>192.64.0.0/10</item>
<item>192.128.0.0/9</item>
<item>192.64.0.0/12</item>
<item>192.80.0.0/13</item>
<item>192.88.0.0/18</item>
<item>192.88.64.0/19</item>
<item>192.88.96.0/23</item>
<item>192.88.98.0/24</item>
<item>192.88.100.0/22</item>
<item>192.88.104.0/21</item>
<item>192.88.112.0/20</item>
<item>192.88.128.0/17</item>
<item>192.89.0.0/16</item>
<item>192.90.0.0/15</item>
<item>192.92.0.0/14</item>
<item>192.96.0.0/11</item>
<item>192.128.0.0/11</item>
<item>192.160.0.0/13</item>
<item>192.169.0.0/16</item>
<item>192.170.0.0/15</item>
<item>192.172.0.0/14</item>
<item>192.176.0.0/12</item>
<item>192.192.0.0/10</item>
<item>193.0.0.0/8</item>
<item>194.0.0.0/7</item>
<item>196.0.0.0/6</item>
<item>200.0.0.0/5</item>
<item>196.0.0.0/7</item>
<item>198.0.0.0/12</item>
<item>198.16.0.0/15</item>
<item>198.20.0.0/14</item>
<item>198.24.0.0/13</item>
<item>198.32.0.0/12</item>
<item>198.48.0.0/15</item>
<item>198.50.0.0/16</item>
<item>198.51.0.0/18</item>
<item>198.51.64.0/19</item>
<item>198.51.96.0/22</item>
<item>198.51.101.0/24</item>
<item>198.51.102.0/23</item>
<item>198.51.104.0/21</item>
<item>198.51.112.0/20</item>
<item>198.51.128.0/17</item>
<item>198.52.0.0/14</item>
<item>198.56.0.0/13</item>
<item>198.64.0.0/10</item>
<item>198.128.0.0/9</item>
<item>199.0.0.0/8</item>
<item>200.0.0.0/7</item>
<item>202.0.0.0/8</item>
<item>203.0.0.0/18</item>
<item>203.0.64.0/19</item>
<item>203.0.96.0/20</item>
<item>203.0.112.0/24</item>
<item>203.0.114.0/23</item>
<item>203.0.116.0/22</item>
<item>203.0.120.0/21</item>
<item>203.0.128.0/17</item>
<item>203.1.0.0/16</item>
<item>203.2.0.0/15</item>
<item>203.4.0.0/14</item>
<item>203.8.0.0/13</item>
<item>203.16.0.0/12</item>
<item>203.32.0.0/11</item>
<item>203.64.0.0/10</item>
<item>203.128.0.0/9</item>
<item>204.0.0.0/6</item>
<item>208.0.0.0/4</item>
</string-array>
</resources>
+1 -1
View File
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="black_list" translatable="false">".appspot.com",".blogspot.com",".blogspot.kr",".blogspot.sg",".cdninstagram.com",".connect.facebook.net",".facebook.br",".facebook.com",".facebook.hu",".facebook.nl",".facebook.se",".fbcdn.net",".google.co.kr",".google.com",".google.com",".google.com.hk",".google.com.sg",".google.com.tw",".googleapis.com",".googleapps.com",".googleartproject.com",".googleblog.com",".googlecode.com",".googlecommerce.com",".googledomains.com",".googledrive.com",".googleearth.com",".googlegroups.com",".googlehosted.com",".googleideas.com",".googlelabs.com",".googlemail.com",".googleplay.com",".googleplus.com",".googlesile.com",".googlesource.com",".googleusercontent.com",".googlevideo.com",".googlezip.net",".gstatic.com",".yahoo.com",".igoogle.com",".instagram.com",".institut-tibetain.org",".nic.google",".registry.google",".thinkwithgoogle.com",".withgoogle.com",".youtube-nocookie.com",".youtube.com",".gvt1.com",".gvt2.com",".gvt3.com",".1e100.net"</string>
<string name="black_list" translatable="false">".appspot.com",".blogspot.com",".blogspot.kr",".blogspot.sg",".cdninstagram.com",".connect.facebook.net",".facebook.br",".facebook.com",".facebook.hu",".facebook.nl",".facebook.se",".fbcdn.net",".google.co.jp",".google.co.kr",".google.com",".google.com",".google.com.hk",".google.com.sg",".google.com.tw",".googleapis.com",".googleapps.com",".googleartproject.com",".googleblog.com",".googlecode.com",".googlecommerce.com",".googledomains.com",".googledrive.com",".googleearth.com",".googlegroups.com",".googlehosted.com",".googleideas.com",".googlelabs.com",".googlemail.com",".googleplay.com",".googleplus.com",".googlesile.com",".googlesource.com",".googleusercontent.com",".googlevideo.com",".googlezip.net",".gstatic.com",".yahoo.com",".igoogle.com",".instagram.com",".institut-tibetain.org",".nic.google",".registry.google",".thinkwithgoogle.com",".withgoogle.com",".youtube-nocookie.com",".youtube.com",".gvt1.com",".gvt2.com",".gvt3.com",".1e100.net"</string>
</resources>
+3
View File
@@ -44,5 +44,8 @@
<item name="android:fadeScrollbars">true</item>
<item name="android:scrollbars">vertical</item>
</style>
<style name="NumberPickerStyle">
<item name="android:textSize">14sp</item>
</style>
</resources>
+17 -20
View File
@@ -2,40 +2,40 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res/com.github.shadowsocks">
<tk.mygod.preference.PreferenceCategory
<be.mygod.preference.PreferenceCategory
android:title="@string/proxy_cat">
<tk.mygod.preference.EditTextPreference
<be.mygod.preference.EditTextPreference
android:key="profileName"
android:persistent="false"
android:summary="%s"
android:title="@string/profile_name"/>
<tk.mygod.preference.EditTextPreference
<be.mygod.preference.EditTextPreference
android:key="proxy"
android:persistent="false"
android:summary="%s"
android:title="@string/proxy"/>
<tk.mygod.preference.NumberPickerPreference
<be.mygod.preference.NumberPickerPreference
app:min="1"
app:max="65535"
android:key="remotePortNum"
android:persistent="false"
android:summary="%d"
android:title="@string/remote_port"/>
<tk.mygod.preference.NumberPickerPreference
<be.mygod.preference.NumberPickerPreference
app:min="1025"
app:max="65535"
android:key="localPortNum"
android:persistent="false"
android:summary="%d"
android:title="@string/port"/>
<tk.mygod.preference.EditTextPreference
<be.mygod.preference.EditTextPreference
android:inputType="textPassword"
android:key="sitekey"
android:persistent="false"
android:summary="%s"
android:title="@string/sitekey"/>
<android.support.v7.preference.DropDownPreference
<DropDownPreference
android:key="encMethod"
android:persistent="false"
android:entries="@array/enc_method_entry"
@@ -48,12 +48,12 @@
android:summary="@string/onetime_auth_summary"
android:title="@string/onetime_auth"/>
</tk.mygod.preference.PreferenceCategory>
</be.mygod.preference.PreferenceCategory>
<tk.mygod.preference.PreferenceCategory
<be.mygod.preference.PreferenceCategory
android:title="@string/feature_cat">
<android.support.v7.preference.DropDownPreference
<DropDownPreference
android:key="route"
android:persistent="false"
android:entries="@array/route_entry"
@@ -76,9 +76,9 @@
android:summary="@string/udp_dns_summary"
android:title="@string/udp_dns"/>
</tk.mygod.preference.PreferenceCategory>
</be.mygod.preference.PreferenceCategory>
<tk.mygod.preference.PreferenceCategory
<be.mygod.preference.PreferenceCategory
android:title="@string/kcp">
<SwitchPreference
@@ -87,7 +87,7 @@
android:summary="@string/kcp_summary"
android:title="@string/kcp"/>
<tk.mygod.preference.NumberPickerPreference
<be.mygod.preference.NumberPickerPreference
app:min="1"
app:max="65535"
android:key="kcpPort"
@@ -95,15 +95,15 @@
android:summary="@string/kcp_port_summary"
android:title="@string/kcp_port"/>
<tk.mygod.preference.EditTextPreference
<be.mygod.preference.EditTextPreference
android:key="kcpcli"
android:persistent="false"
android:summary="%s"
android:title="@string/kcpcli"/>
</tk.mygod.preference.PreferenceCategory>
</be.mygod.preference.PreferenceCategory>
<tk.mygod.preference.PreferenceCategory android:title="@string/misc_cat">
<be.mygod.preference.PreferenceCategory android:title="@string/misc_cat">
<SwitchPreference
android:key="isAutoConnect"
@@ -121,12 +121,9 @@
android:summary="@string/nat_summary"/>
<Preference android:key="recovery" android:title="@string/recovery"
android:summary="@string/recovery_summary"/>
<Preference android:key="flush_dnscache"
android:title="@string/flush_dnscache"
android:summary="@string/flush_dnscache_summary"/>
<Preference android:key="about" android:title="@string/about"/>
</tk.mygod.preference.PreferenceCategory>
</be.mygod.preference.PreferenceCategory>
</PreferenceScreen>
+27
View File
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
<shortcut android:shortcutId="toggle"
android:icon="@drawable/ic_qu_shadowsocks_launcher"
android:shortcutShortLabel="@string/quick_toggle"
android:shortcutLongLabel="@string/quick_toggle">
<intent android:action="android.intent.action.MAIN"
android:targetPackage="com.github.shadowsocks"
android:targetClass="com.github.shadowsocks.QuickToggleShortcut" />
</shortcut>
<shortcut android:shortcutId="switch"
android:icon="@drawable/ic_qu_shadowsocks_launcher"
android:shortcutShortLabel="@string/quick_switch"
android:shortcutLongLabel="@string/quick_switch">
<intent android:action="android.intent.action.MAIN"
android:targetPackage="com.github.shadowsocks"
android:targetClass="com.github.shadowsocks.ShadowsocksQuickSwitchActivity" />
</shortcut>
<shortcut android:shortcutId="scan"
android:icon="@drawable/ic_qu_camera_launcher"
android:shortcutShortLabel="@string/add_profile_methods_scan_qr_code"
android:shortcutLongLabel="@string/add_profile_methods_scan_qr_code">
<intent android:action="android.intent.action.MAIN"
android:targetPackage="com.github.shadowsocks"
android:targetClass="com.github.shadowsocks.ScannerActivity" />
</shortcut>
</shortcuts>
@@ -0,0 +1,8 @@
package be.mygod.preference
import android.app.DialogFragment
import android.support.v7.preference.DialogPreference
trait DialogPreferencePlus extends DialogPreference {
def createDialog(): DialogFragment
}
@@ -1,4 +1,4 @@
package tk.mygod.preference
package be.mygod.preference
import android.content.Context
import android.support.v7.preference.{EditTextPreference => Parent}
@@ -11,10 +11,12 @@ import android.util.AttributeSet
* Based on: https://github.com/Gericop/Android-Support-Preference-V7-Fix/tree/master/app/src/main/java/android/support/v7/preference
*/
class EditTextPreference(context: Context, attrs: AttributeSet = null) extends Parent(context, attrs)
with SummaryPreference {
with DialogPreferencePlus with SummaryPreference {
val editText = new AppCompatEditText(context, attrs)
editText.setId(android.R.id.edit)
override def createDialog() = new EditTextPreferenceDialogFragment()
override protected def getSummaryValue = {
var text = getText
if (text == null) text = ""
@@ -1,4 +1,4 @@
package tk.mygod.preference
package be.mygod.preference
import android.content.Context
import android.support.v14.preference.PreferenceDialogFragment
@@ -1,40 +1,16 @@
package tk.mygod.preference
package be.mygod.preference
import android.content.Context
import android.content.res.TypedArray
import android.support.v7.preference.DialogPreference
import android.util.AttributeSet
import android.view.View
import android.view.ViewGroup.LayoutParams
import android.view.ContextThemeWrapper
import android.widget.NumberPicker
import android.widget.EditText
import com.github.shadowsocks.R
class MyNumberPicker(private val context: Context, attrs: AttributeSet = null)
extends NumberPicker(context, attrs) {
override def addView(child: View) {
super.addView(child)
updateView(child)
}
override def addView(child: View, params: LayoutParams) {
super.addView(child, params)
updateView(child)
}
override def addView(child: View, index: Int, params: LayoutParams) {
super.addView(child, index, params)
updateView(child)
}
def updateView(child: View) {
if (child.isInstanceOf[EditText]) {
child.asInstanceOf[EditText].setTextSize(dp2px(6))
}
}
def dp2px(dp: Float): Int = (dp*getResources().getDisplayMetrics().density).toInt
}
class NumberPickerPreference(private val context: Context, attrs: AttributeSet = null)
extends DialogPreference(context, attrs) with SummaryPreference {
private[preference] val picker = new MyNumberPicker(context)
extends DialogPreference(context, attrs) with DialogPreferencePlus with SummaryPreference {
private[preference] val picker = new NumberPicker(new ContextThemeWrapper(context, R.style.NumberPickerStyle))
private var value: Int = _
{
@@ -44,6 +20,8 @@ class NumberPickerPreference(private val context: Context, attrs: AttributeSet =
a.recycle
}
override def createDialog() = new NumberPickerPreferenceDialogFragment()
def getValue = value
def getMin = if (picker == null) 0 else picker.getMinValue
def getMax = picker.getMaxValue
@@ -1,15 +1,12 @@
package tk.mygod.preference
package be.mygod.preference
import android.content.Context
import android.support.v14.preference.PreferenceDialogFragment
import android.view.inputmethod.InputMethodManager
import android.view.{View, ViewGroup}
class NumberPickerPreferenceDialogFragment extends PreferenceDialogFragment {
private lazy val preference = getPreference.asInstanceOf[NumberPickerPreference]
private lazy val picker = preference.picker
private lazy val inputMethodManager =
getActivity.getSystemService(Context.INPUT_METHOD_SERVICE).asInstanceOf[InputMethodManager]
override protected def onCreateDialogView(context: Context) = {
val parent = picker.getParent.asInstanceOf[ViewGroup]
@@ -30,7 +27,5 @@ class NumberPickerPreferenceDialogFragment extends PreferenceDialogFragment {
val value = picker.getValue
if (preference.callChangeListener(value)) preference.setValue(value)
}
inputMethodManager.hideSoftInputFromWindow(getActivity.getCurrentFocus.getWindowToken,
InputMethodManager.HIDE_NOT_ALWAYS)
}
}
@@ -1,4 +1,4 @@
package tk.mygod.preference
package be.mygod.preference
import android.content.Context
import android.support.v7.preference.{PreferenceViewHolder, PreferenceCategory => Base}
@@ -1,9 +1,9 @@
package tk.mygod.preference
package be.mygod.preference
import android.app.DialogFragment
import android.os.Bundle
import android.support.v14.preference.{PreferenceFragment => Base}
import android.support.v7.preference.PreferenceScreen
import android.support.v7.preference.{Preference, PreferenceScreen}
import android.view.{LayoutInflater, ViewGroup}
abstract class PreferenceFragment extends Base {
@@ -15,7 +15,14 @@ abstract class PreferenceFragment extends Base {
bundle.putString("key", key)
fragment.setArguments(bundle)
fragment.setTargetFragment(this, 0)
fragment.show(getFragmentManager, "android.support.v14.preference.PreferenceFragment.DIALOG")
getFragmentManager.beginTransaction()
.add(fragment, "android.support.v14.preference.PreferenceFragment.DIALOG")
.commitAllowingStateLoss()
}
override def onDisplayPreferenceDialog(preference: Preference) = preference match {
case dpp: DialogPreferencePlus => displayPreferenceDialog(preference.getKey, dpp.createDialog())
case _ => super.onDisplayPreferenceDialog(preference)
}
override protected def onCreateAdapter(screen: PreferenceScreen) = new PreferenceGroupAdapter(screen)
@@ -1,4 +1,4 @@
package tk.mygod.preference
package be.mygod.preference
import java.lang.reflect.Field
import java.util.List
@@ -1,4 +1,4 @@
package tk.mygod.preference
package be.mygod.preference
import android.support.v7.preference.Preference
@@ -42,6 +42,7 @@ package com.github.shadowsocks
import java.util.concurrent.atomic.AtomicBoolean
import android.Manifest.permission
import android.app.TaskStackBuilder
import android.content._
import android.content.pm.PackageManager
import android.graphics.drawable.Drawable
@@ -220,9 +221,11 @@ class AppManager extends AppCompatActivity with OnMenuItemClickListener {
toolbar = findViewById(R.id.toolbar).asInstanceOf[Toolbar]
toolbar.setTitle(R.string.proxied_apps)
toolbar.setNavigationIcon(R.drawable.abc_ic_ab_back_material)
toolbar.setNavigationOnClickListener((v: View) => {
toolbar.setNavigationOnClickListener(_ => {
val intent = getParentActivityIntent
if (intent == null) finish else navigateUpTo(intent)
if (shouldUpRecreateTask(intent) || isTaskRoot)
TaskStackBuilder.create(this).addNextIntentWithParentStack(intent).startActivities()
else finish()
})
toolbar.inflateMenu(R.menu.app_manager_menu)
toolbar.setOnMenuItemClickListener(this)
@@ -50,10 +50,10 @@ 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._
import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.aidl.{IShadowsocksService, IShadowsocksServiceCallback}
import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.utils._
trait BaseService extends Service {
@@ -62,6 +62,7 @@ trait BaseService extends Service {
case class NameNotResolvedException() extends IOException
case class KcpcliParseException(cause: Throwable) extends Exception(cause)
case class NullConnectionException() extends NullPointerException
var timer: Timer = _
var trafficMonitorThread: TrafficMonitorThread = _
@@ -141,8 +142,7 @@ trait BaseService extends Service {
}
def checkProfile(profile: Profile) = if (TextUtils.isEmpty(profile.host) || TextUtils.isEmpty(profile.password)) {
changeState(State.STOPPED, getString(R.string.proxy_empty))
stopRunner(true)
stopRunner(true, getString(R.string.proxy_empty))
false
} else true
@@ -200,6 +200,7 @@ trait BaseService extends Service {
case _: NameNotResolvedException => stopRunner(true, getString(R.string.invalid_server))
case exc: KcpcliParseException =>
stopRunner(true, getString(R.string.service_failed) + ": " + exc.cause.getMessage)
case _: NullConnectionException => stopRunner(true, getString(R.string.reboot_required))
case exc: Throwable =>
stopRunner(true, getString(R.string.service_failed) + ": " + exc.getMessage)
exc.printStackTrace()
@@ -277,6 +278,7 @@ trait BaseService extends Service {
override def onCreate() {
super.onCreate()
app.refreshContainerHolder
app.updateAssets()
}
// Service of shadowsocks should always be started explicitly
@@ -2,12 +2,12 @@ package com.github.shadowsocks
import java.nio.charset.Charset
import android.app.Activity
import android.app.{Activity, TaskStackBuilder}
import android.content._
import android.content.pm.PackageManager
import android.nfc.NfcAdapter.CreateNdefMessageCallback
import android.nfc.{NdefMessage, NdefRecord, NfcAdapter, NfcEvent}
import android.os.{Bundle, Handler}
import android.os.{Build, Bundle, Handler, UserManager}
import android.provider.Settings
import android.support.v7.app.{AlertDialog, AppCompatActivity}
import android.support.v7.widget.RecyclerView.ViewHolder
@@ -86,7 +86,7 @@ final class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClic
val builder = new SpannableStringBuilder
val tx = item.tx + txTotal
val rx = item.rx + rxTotal
builder.append(item.name)
builder.append(if (isDemoMode) "Profile #" + item.id else item.name)
if (tx != 0 || rx != 0) {
val start = builder.length
builder.append(getString(R.string.stat_profiles,
@@ -186,6 +186,7 @@ final class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClic
private var undoManager: UndoSnackbarManager[Profile] = _
private lazy val clipboard = getSystemService(Context.CLIPBOARD_SERVICE).asInstanceOf[ClipboardManager]
private lazy val isDemoMode = Build.VERSION.SDK_INT >= 25 && getSystemService(classOf[UserManager]).isDemoUser
private var nfcAdapter : NfcAdapter = _
private var nfcShareItem: Array[Byte] = _
@@ -202,9 +203,11 @@ final class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClic
val toolbar = findViewById(R.id.toolbar).asInstanceOf[Toolbar]
toolbar.setTitle(R.string.profiles)
toolbar.setNavigationIcon(R.drawable.ic_navigation_close)
toolbar.setNavigationOnClickListener((v: View) => {
toolbar.setNavigationOnClickListener(_ => {
val intent = getParentActivityIntent
if (intent == null) finish else navigateUpTo(intent)
if (shouldUpRecreateTask(intent) || isTaskRoot)
TaskStackBuilder.create(this).addNextIntentWithParentStack(intent).startActivities()
else finish()
})
toolbar.inflateMenu(R.menu.profile_manager_menu)
toolbar.setOnMenuItemClickListener(this)
@@ -0,0 +1,42 @@
package com.github.shadowsocks
import android.app.Activity
import android.content.Intent
import android.content.pm.ShortcutManager
import android.os.{Build, Bundle}
import com.github.shadowsocks.utils.{State, Utils}
/**
* @author Mygod
*/
class QuickToggleShortcut extends Activity with ServiceBoundContext {
override def onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
getIntent.getAction match {
case Intent.ACTION_CREATE_SHORTCUT =>
setResult(Activity.RESULT_OK, new Intent()
.putExtra(Intent.EXTRA_SHORTCUT_INTENT, new Intent(this, classOf[QuickToggleShortcut]))
.putExtra(Intent.EXTRA_SHORTCUT_NAME, getString(R.string.quick_toggle))
.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
Intent.ShortcutIconResource.fromContext(this, R.mipmap.ic_launcher)))
finish()
case _ =>
attachService()
if (Build.VERSION.SDK_INT >= 25) getSystemService(classOf[ShortcutManager]).reportShortcutUsed("toggle")
}
}
override def onDestroy() {
detachService()
super.onDestroy()
}
override def onServiceConnected() {
bgService.getState match {
case State.STOPPED => Utils.startSsService(this)
case State.CONNECTED => Utils.stopSsService(this)
case _ => // ignore
}
finish()
}
}
@@ -1,23 +1,25 @@
package com.github.shadowsocks
import android.app.Activity
import android.app.{Activity, TaskStackBuilder}
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Bundle
import android.content.pm.{PackageManager, ShortcutManager}
import android.os.{Build, Bundle}
import android.support.v4.app.ActivityCompat
import android.support.v4.content.ContextCompat
import android.support.v7.app.AppCompatActivity
import android.support.v7.widget.Toolbar
import android.view.{MenuItem, ViewGroup}
import android.widget.Toast
import com.google.zxing.Result
import me.dm7.barcodescanner.zxing.ZXingScannerView
object ScannerActivity {
private final val MY_PERMISSIONS_REQUEST_CAMERA = 1
}
class ScannerActivity extends AppCompatActivity with ZXingScannerView.ResultHandler {
import ScannerActivity._
val MY_PERMISSIONS_REQUEST_CAMERA = 1
var scannerView: ZXingScannerView = _
private var scannerView: ZXingScannerView = _
override def onRequestPermissionsResult(requestCode: Int, permissions: Array[String],
grantResults: Array[Int]) {
@@ -34,14 +36,22 @@ class ScannerActivity extends AppCompatActivity with ZXingScannerView.ResultHand
}
}
def navigateUp() {
val intent = getParentActivityIntent
if (shouldUpRecreateTask(intent) || isTaskRoot)
TaskStackBuilder.create(this).addNextIntentWithParentStack(intent).startActivities()
else finish()
}
override def onCreate(state: Bundle) {
super.onCreate(state)
setContentView(R.layout.layout_scanner)
setupToolbar()
scannerView = new ZXingScannerView(this)
val contentFrame = findViewById(R.id.content_frame).asInstanceOf[ViewGroup]
contentFrame.addView(scannerView)
val toolbar = findViewById(R.id.toolbar).asInstanceOf[Toolbar]
toolbar.setTitle(getTitle)
toolbar.setNavigationIcon(R.drawable.abc_ic_ab_back_material)
toolbar.setNavigationOnClickListener(_ => navigateUp())
scannerView = findViewById(R.id.scanner).asInstanceOf[ZXingScannerView]
if (Build.VERSION.SDK_INT >= 25) getSystemService(classOf[ShortcutManager]).reportShortcutUsed("scan")
}
override def onResume() {
@@ -66,29 +76,7 @@ class ScannerActivity extends AppCompatActivity with ZXingScannerView.ResultHand
val intent = new Intent()
intent.putExtra("uri", rawResult.getText)
setResult(Activity.RESULT_OK, intent)
finish()
navigateUp()
}
def setupToolbar() {
val toolbar = findViewById(R.id.toolbar).asInstanceOf[Toolbar]
setSupportActionBar(toolbar)
val ab = getSupportActionBar()
if (ab != null) {
ab.setDisplayHomeAsUpEnabled(true)
}
}
override def onOptionsItemSelected(item: MenuItem): Boolean = {
item.getItemId() match {
// Respond to the action bar's Up/Home button
case android.R.id.home =>
setResult(Activity.RESULT_CANCELED, new Intent())
finish()
return true
case _ => // Do nothing
}
return super.onOptionsItemSelected(item)
}
}
@@ -38,17 +38,14 @@
*/
package com.github.shadowsocks
import java.io.{FileOutputStream, IOException, InputStream, OutputStream}
import java.lang.System.currentTimeMillis
import java.net.{HttpURLConnection, URL}
import java.util.Date
import java.util.Hashtable
import java.util.Locale
import java.util
import java.util.{GregorianCalendar, Locale}
import android.app.backup.BackupManager
import android.app.{Activity, ProgressDialog}
import android.content._
import android.content.res.AssetManager
import android.graphics.Typeface
import android.net.VpnService
import android.os._
@@ -66,9 +63,7 @@ import com.github.shadowsocks.database._
import com.github.shadowsocks.utils.CloseUtils._
import com.github.shadowsocks.utils._
import com.google.android.gms.ads.{AdRequest, AdSize, AdView}
import eu.chainfire.libsuperuser.Shell
import scala.collection.mutable.ArrayBuffer
import scala.util.Random
object Typefaces {
@@ -89,15 +84,13 @@ object Typefaces {
}
private final val TAG = "Typefaces"
private final val cache = new Hashtable[String, Typeface]
private final val cache = new util.Hashtable[String, Typeface]
}
object Shadowsocks {
// Constants
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, Executable.KCPTUN)
}
class Shadowsocks extends AppCompatActivity with ServiceBoundContext {
@@ -187,14 +180,6 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext {
snackbar.setAction(R.string.switch_to_vpn, (_ => preferences.natSwitch.setChecked(false)): View.OnClickListener)
snackbar.show
}
if (app.settings.getInt(Key.currentVersionCode, -1) != BuildConfig.VERSION_CODE) {
app.editor.putInt(Key.currentVersionCode, BuildConfig.VERSION_CODE).apply()
recovery()
updateCurrentProfile()
}
}
override def onServiceDisconnected() {
@@ -202,8 +187,8 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext {
}
override def binderDied {
detachService
crashRecovery
detachService()
app.crashRecovery()
attachService
}
@@ -241,70 +226,6 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext {
}
}
private def copyAssets(path: String) {
val assetManager: AssetManager = getAssets
var files: Array[String] = null
try {
files = assetManager.list(path)
} catch {
case e: IOException =>
Log.e(TAG, e.getMessage)
app.track(e)
}
if (files != null) {
for (file <- files) {
var in: InputStream = null
var out: OutputStream = null
try {
if (path.length > 0) {
in = assetManager.open(path + "/" + file)
} else {
in = assetManager.open(file)
}
out = new FileOutputStream(getApplicationInfo.dataDir + "/" + file)
copyFile(in, out)
in.close()
in = null
out.flush()
out.close()
out = null
} catch {
case ex: Exception =>
Log.e(TAG, ex.getMessage)
app.track(ex)
}
}
}
}
private def copyFile(in: InputStream, out: OutputStream) {
val buffer: Array[Byte] = new Array[Byte](1024)
var read: Int = 0
while ( {
read = in.read(buffer)
read
} != -1) {
out.write(buffer, 0, read)
}
}
def crashRecovery() {
val cmd = new ArrayBuffer[String]()
for (task <- Array("ss-local", "ss-tunnel", "pdnsd", "redsocks", "tun2socks", "kcptun")) {
cmd.append("killall %s".formatLocal(Locale.ENGLISH, task))
cmd.append("rm -f %1$s/%2$s-nat.conf %1$s/%2$s-vpn.conf"
.formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir, task))
}
if (app.isNatEnabled) {
cmd.append("iptables -t nat -F OUTPUT")
cmd.append("echo done")
val result = Shell.SU.run(cmd.toArray)
if (result != null && !result.isEmpty) return // fallback to SH
}
Shell.SH.run(cmd.toArray)
}
def cancelStart() {
clearDialog()
changeSwitch(checked = false)
@@ -431,10 +352,11 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext {
preferences.setEnabled(false)
fabProgressCircle.postDelayed(hideCircle, 100)
stat.setVisibility(View.VISIBLE)
if (resetConnectionTest) if (app.isNatEnabled) connectionTestText.setVisibility(View.GONE) else {
connectionTestText.setVisibility(View.VISIBLE)
connectionTestText.setText(getString(R.string.connection_test_pending))
}
if (resetConnectionTest || state != State.CONNECTED)
if (app.isNatEnabled) connectionTestText.setVisibility(View.GONE) else {
connectionTestText.setVisibility(View.VISIBLE)
connectionTestText.setText(getString(R.string.connection_test_pending))
}
case State.STOPPING =>
fab.setBackgroundTintList(greyTint)
serviceStarted = false
@@ -497,7 +419,7 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext {
val year = 1975 + random.nextInt(40)
val month = 1 + random.nextInt(12)
val day = random.nextInt(28)
adBuilder.setBirthday(new Date(year, month, day))
adBuilder.setBirthday(new GregorianCalendar(year, month, day).getTime)
// Load Ad
adView.loadAd(adBuilder.build())
@@ -523,33 +445,11 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext {
handler.removeCallbacksAndMessages(null)
}
def reset() {
crashRecovery()
copyAssets(System.getABI)
copyAssets("acl")
val ab = new ArrayBuffer[String]
for (executable <- EXECUTABLES) {
ab.append("chmod 755 " + getApplicationInfo.dataDir + "/" + executable)
}
Shell.SH.run(ab.toArray)
}
def recovery() {
if (serviceStarted) serviceStop()
val h = showProgress(R.string.recovering)
Utils.ThrowableFuture {
reset()
h.sendEmptyMessage(0)
}
}
def flushDnsCache() {
val h = showProgress(R.string.flushing)
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)
app.copyAssets()
h.sendEmptyMessage(0)
}
}
@@ -39,21 +39,43 @@
package com.github.shadowsocks
import java.io.{FileOutputStream, IOException}
import java.util
import java.util.Locale
import java.util.concurrent.TimeUnit
import android.annotation.SuppressLint
import android.app.Application
import android.content.res.Configuration
import android.os.{Build, LocaleList}
import android.preference.PreferenceManager
import android.support.v7.app.AppCompatDelegate
import android.util.Log
import com.evernote.android.job.JobManager
import com.github.shadowsocks.database.{DBHelper, ProfileManager}
import com.github.shadowsocks.utils.{Key, Utils, TcpFastOpen}
import com.github.shadowsocks.job.DonaldTrump
import com.github.shadowsocks.utils.CloseUtils._
import com.github.shadowsocks.utils._
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
import eu.chainfire.libsuperuser.Shell
import scala.collection.mutable.ArrayBuffer
object ShadowsocksApplication {
var app: ShadowsocksApplication = _
private final val TAG = "ShadowsocksApplication"
private val EXECUTABLES = Array(Executable.PDNSD, Executable.REDSOCKS, Executable.SS_TUNNEL, Executable.SS_LOCAL,
Executable.TUN2SOCKS, Executable.KCPTUN)
// The ones in Locale doesn't have script included
private final lazy val SIMPLIFIED_CHINESE =
if (Build.VERSION.SDK_INT >= 21) Locale.forLanguageTag("zh-Hans-CN") else Locale.SIMPLIFIED_CHINESE
private final lazy val TRADITIONAL_CHINESE =
if (Build.VERSION.SDK_INT >= 21) Locale.forLanguageTag("zh-Hant-TW") else Locale.TRADITIONAL_CHINESE
}
class ShadowsocksApplication extends Application {
@@ -88,10 +110,62 @@ class ShadowsocksApplication extends Application {
profileManager.getProfile(id) getOrElse profileManager.createProfile()
}
private def checkChineseLocale(locale: Locale): Locale = if (locale.getLanguage == "zh") locale.getCountry match {
case "CN" | "TW" => null // already supported
case _ => locale.getScript match { // fallback to the corresponding script
case "Hans" => SIMPLIFIED_CHINESE
case "Hant" => TRADITIONAL_CHINESE
case script =>
Log.w(TAG, "Unknown zh locale script: %s. Falling back to trying countries...".format(script))
locale.getCountry match {
case "SG" => SIMPLIFIED_CHINESE
case "HK" | "MO" => TRADITIONAL_CHINESE
case _ =>
Log.w(TAG, "Unknown zh locale: %s. Falling back to zh-Hans-CN...".format(locale.toLanguageTag))
SIMPLIFIED_CHINESE
}
}
} else null
@SuppressLint(Array("NewApi"))
private def checkChineseLocale(config: Configuration): Unit = if (Build.VERSION.SDK_INT >= 24) {
val localeList = config.getLocales
val newList = new Array[Locale](localeList.size())
var changed = false
for (i <- 0 until localeList.size()) {
val locale = localeList.get(i)
val newLocale = checkChineseLocale(locale)
if (newLocale == null) newList(i) = locale else {
newList(i) = newLocale
changed = true
}
}
if (changed) {
val newConfig = new Configuration(config)
newConfig.setLocales(new LocaleList(newList.distinct: _*))
val res = getResources
res.updateConfiguration(newConfig, res.getDisplayMetrics)
}
} else {
val newLocale = checkChineseLocale(config.locale)
if (newLocale != null) {
val newConfig = new Configuration(config)
newConfig.locale = newLocale
val res = getResources
res.updateConfiguration(newConfig, res.getDisplayMetrics)
}
}
override def onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
checkChineseLocale(newConfig)
}
override def onCreate() {
if (!BuildConfig.DEBUG) java.lang.System.setProperty(LocalLog.LOCAL_LOG_LEVEL_PROPERTY, "ERROR")
app = this
if (!BuildConfig.DEBUG) java.lang.System.setProperty(LocalLog.LOCAL_LOG_LEVEL_PROPERTY, "ERROR")
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
checkChineseLocale(getResources.getConfiguration)
val tm = TagManager.getInstance(this)
val pending = tm.loadContainerPreferNonDefault("GTM-NT8WS8", R.raw.gtm_default_container)
val callback = new ResultCallback[ContainerHolder] {
@@ -111,6 +185,7 @@ class ShadowsocksApplication extends Application {
}
}
pending.setResultCallback(callback, 2, TimeUnit.SECONDS)
JobManager.create(this).addJobCreator(DonaldTrump)
TcpFastOpen.enabled(settings.getBoolean(Key.tfo, TcpFastOpen.sendEnabled))
}
@@ -119,4 +194,45 @@ class ShadowsocksApplication extends Application {
val holder = app.containerHolder
if (holder != null) holder.refresh()
}
private def copyAssets(path: String) {
val assetManager = getAssets
var files: Array[String] = null
try files = assetManager.list(path) catch {
case e: IOException =>
Log.e(TAG, e.getMessage)
app.track(e)
}
if (files != null) for (file <- files)
autoClose(assetManager.open(if (path.nonEmpty) path + '/' + file else file))(in =>
autoClose(new FileOutputStream(getApplicationInfo.dataDir + '/' + file))(out =>
IOUtils.copy(in, out)))
}
def crashRecovery() {
val cmd = new ArrayBuffer[String]()
for (task <- Array("ss-local", "ss-tunnel", "pdnsd", "redsocks", "tun2socks", "kcptun")) {
cmd.append("killall %s".formatLocal(Locale.ENGLISH, task))
cmd.append("rm -f %1$s/%2$s-nat.conf %1$s/%2$s-vpn.conf"
.formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir, task))
}
if (app.isNatEnabled) {
cmd.append("iptables -t nat -F OUTPUT")
cmd.append("echo done")
val result = Shell.SU.run(cmd.toArray)
if (result != null && !result.isEmpty) return // fallback to SH
}
Shell.SH.run(cmd.toArray)
}
def copyAssets() {
crashRecovery() // ensure executables are killed before writing to them
copyAssets(System.getABI)
copyAssets("acl")
Shell.SH.run(EXECUTABLES.map("chmod 755 " + getApplicationInfo.dataDir + '/' + _))
editor.putInt(Key.currentVersionCode, BuildConfig.VERSION_CODE).apply()
}
def updateAssets() = if (settings.getInt(Key.currentVersionCode, -1) != BuildConfig.VERSION_CODE) copyAssets()
}
@@ -48,6 +48,7 @@ import android.os._
import android.util.Log
import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.job.AclSyncJob
import com.github.shadowsocks.utils._
import eu.chainfire.libsuperuser.Shell
@@ -96,13 +97,7 @@ class ShadowsocksNatService extends BaseService {
if (profile.route != Route.ALL) {
cmd += "--acl"
profile.route match {
case Route.BYPASS_LAN => cmd += (getApplicationInfo.dataDir + "/bypass_lan.acl")
case Route.BYPASS_CHN => cmd += (getApplicationInfo.dataDir + "/bypass_chn.acl")
case Route.BYPASS_LAN_CHN => cmd += (getApplicationInfo.dataDir + "/bypass_lan_chn.acl")
case Route.GFWLIST => cmd += (getApplicationInfo.dataDir + "/gfwlist.acl")
case Route.CHINALIST => cmd += (getApplicationInfo.dataDir + "/chinalist.acl")
}
cmd += getApplicationInfo.dataDir + '/' + profile.route + ".acl"
}
if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" "))
@@ -112,8 +107,14 @@ class ShadowsocksNatService extends BaseService {
def startKcptunDaemon() {
if (profile.kcpcli == null) profile.kcpcli = ""
val host = if (profile.host.contains(":")) {
"[" + profile.host + "]"
} else {
profile.host
}
val cmd = ArrayBuffer[String](getApplicationInfo.dataDir + "/kcptun"
, "-r", profile.host + ":" + profile.kcpPort
, "-r", host + ":" + profile.kcpPort
, "-l", "127.0.0.1:" + (profile.localPort + 90))
try cmd ++= Utils.translateCommandline(profile.kcpcli) catch {
case exc: Exception => throw KcpcliParseException(exc)
@@ -188,12 +189,12 @@ class ShadowsocksNatService extends BaseService {
val conf = profile.route match {
case Route.BYPASS_CHN | Route.BYPASS_LAN_CHN | Route.GFWLIST => {
ConfigUtils.PDNSD_DIRECT.formatLocal(Locale.ENGLISH, "", getApplicationInfo.dataDir,
"127.0.0.1", profile.localPort + 53, "1.2.4.8, 114.114.114.114",
"127.0.0.1", profile.localPort + 53, "114.114.114.114, 223.5.5.5, 1.2.4.8",
getBlackList, reject, profile.localPort + 63, reject)
}
case Route.CHINALIST => {
ConfigUtils.PDNSD_DIRECT.formatLocal(Locale.ENGLISH, "", getApplicationInfo.dataDir,
"127.0.0.1", profile.localPort + 53, "8.8.8.8, 208.67.222.222",
"127.0.0.1", profile.localPort + 53, "8.8.8.8, 8.8.4.4, 208.67.222.222",
"", reject, profile.localPort + 63, reject)
}
case _ => {
@@ -330,8 +331,10 @@ class ShadowsocksNatService extends BaseService {
}
handleConnection()
// Set DNS
su.addCommand(Utils.FLUSH_DNS)
if (profile.route != Route.ALL)
AclSyncJob.schedule(profile.route)
changeState(State.CONNECTED)
notification = new ShadowsocksNotification(this, profile.name, true)
}
@@ -40,7 +40,7 @@ class ShadowsocksNotification(private val service: BaseService, profileName: Str
.setContentIntent(PendingIntent.getActivity(service, 0, new Intent(service, classOf[Shadowsocks])
.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT), 0))
.setSmallIcon(R.drawable.ic_stat_shadowsocks)
if (!ShadowsocksTileService.running) builder.addAction(R.drawable.ic_navigation_close,
builder.addAction(R.drawable.ic_navigation_close,
service.getString(R.string.stop), PendingIntent.getBroadcast(service, 0, new Intent(Action.CLOSE), 0))
app.profileManager.getAllProfiles match {
case Some(profiles) => if (profiles.length > 1)
@@ -1,5 +1,6 @@
package com.github.shadowsocks
import android.content.pm.ShortcutManager
import android.content.res.Resources
import android.os.{Build, Bundle}
import android.support.v7.app.AppCompatActivity
@@ -70,5 +71,6 @@ class ShadowsocksQuickSwitchActivity extends AppCompatActivity {
if (app.profileId >= 0) lm.scrollToPosition(profilesAdapter.profiles.zipWithIndex.collectFirst {
case (profile, i) if profile.id == app.profileId => i
}.getOrElse(0))
if (Build.VERSION.SDK_INT >= 25) getSystemService(classOf[ShortcutManager]).reportShortcutUsed("switch")
}
}
@@ -58,7 +58,7 @@ class ShadowsocksRunnerService extends Service with ServiceBoundContext {
if (bgService != null) {
if (app.isNatEnabled) startBackgroundService()
else if (VpnService.prepare(ShadowsocksRunnerService.this) == null) startBackgroundService()
handler.postDelayed(() => stopSelf(), 10000)
handler.postDelayed(() => stopSelf(), 3000)
}
}, 1000)
}
@@ -5,7 +5,7 @@ import java.util.Locale
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import android.content.{Intent, SharedPreferences}
import android.net.Uri
import android.os.Bundle
import android.os.{Build, Bundle, UserManager}
import android.support.design.widget.Snackbar
import android.support.v14.preference.SwitchPreference
import android.support.v7.app.AlertDialog
@@ -15,7 +15,7 @@ import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.preferences.KcpCliPreferenceDialogFragment
import com.github.shadowsocks.utils.{Key, TcpFastOpen, Utils}
import tk.mygod.preference._
import be.mygod.preference._
object ShadowsocksSettings {
// Constants
@@ -41,13 +41,21 @@ object ShadowsocksSettings {
pref.asInstanceOf[SwitchPreference].setChecked(value)
}
def updatePreference(pref: Preference, name: String, profile: Profile) {
def updatePreference(pref: Preference, name: String, profile: Profile, demo: Boolean = false) {
name match {
case Key.name => updateEditTextPreference(pref, profile.name)
case Key.host => updateEditTextPreference(pref, profile.host)
case Key.remotePort => updateNumberPickerPreference(pref, profile.remotePort)
case Key.name =>
updateEditTextPreference(pref, profile.name)
pref.setSummary(if (demo) "Profile #" + profile.id else "%s")
case Key.host =>
updateEditTextPreference(pref, profile.host)
pref.setSummary(if (demo) "shadowsocks.example.org" else "%s")
case Key.remotePort =>
updateNumberPickerPreference(pref, profile.remotePort)
pref.setSummary(if (demo) "1337" else "%d")
case Key.localPort => updateNumberPickerPreference(pref, profile.localPort)
case Key.password => updateEditTextPreference(pref, profile.password)
case Key.password =>
updateEditTextPreference(pref, profile.password)
pref.setSummary(if (demo) "\u2022" * 32 else "%s")
case Key.method => updateDropDownPreference(pref, profile.method)
case Key.route => updateDropDownPreference(pref, profile.route)
case Key.proxyApps => updateSwitchPreference(pref, profile.proxyApps)
@@ -170,12 +178,6 @@ class ShadowsocksSettings extends PreferenceFragment with OnSharedPreferenceChan
true
})
findPreference("flush_dnscache").setOnPreferenceClickListener(_ => {
app.track(TAG, "flush_dnscache")
activity.flushDnsCache()
true
})
findPreference("about").setOnPreferenceClickListener((preference: Preference) => {
app.track(TAG, "about")
val web = new WebView(activity)
@@ -201,12 +203,8 @@ class ShadowsocksSettings extends PreferenceFragment with OnSharedPreferenceChan
})
}
override def onDisplayPreferenceDialog(preference: Preference) = preference match {
case _: EditTextPreference => displayPreferenceDialog(preference.getKey,
if (preference.getKey == Key.kcpcli) new KcpCliPreferenceDialogFragment()
else new EditTextPreferenceDialogFragment())
case _: NumberPickerPreference =>
displayPreferenceDialog(preference.getKey, new NumberPickerPreferenceDialogFragment())
override def onDisplayPreferenceDialog(preference: Preference) = preference.getKey match {
case Key.kcpcli => displayPreferenceDialog(Key.kcpcli, new KcpCliPreferenceDialogFragment())
case _ => super.onDisplayPreferenceDialog(preference)
}
@@ -254,6 +252,7 @@ class ShadowsocksSettings extends PreferenceFragment with OnSharedPreferenceChan
var profile: Profile = _
def setProfile(profile: Profile) {
this.profile = profile
for (name <- Array(PROXY_PREFS, FEATURE_PREFS).flatten) updatePreference(findPreference(name), name, profile)
val demo = Build.VERSION.SDK_INT >= 25 && activity.getSystemService(classOf[UserManager]).isDemoUser
for (name <- Array(PROXY_PREFS, FEATURE_PREFS).flatten) updatePreference(findPreference(name), name, profile, demo)
}
}
@@ -9,13 +9,8 @@ import com.github.shadowsocks.utils.{State, Utils}
/**
* @author Mygod
*/
object ShadowsocksTileService {
var running: Boolean = _
}
@TargetApi(24)
final class ShadowsocksTileService extends TileService with ServiceBoundContext {
import ShadowsocksTileService._
private lazy val iconIdle = Icon.createWithResource(this, R.drawable.ic_start_idle).setTint(0x80ffffff)
private lazy val iconBusy = Icon.createWithResource(this, R.drawable.ic_start_busy)
@@ -46,15 +41,6 @@ final class ShadowsocksTileService extends TileService with ServiceBoundContext
override def onServiceConnected() = callback.stateChanged(bgService.getState, bgService.getProfileName, null)
override def onCreate {
super.onCreate
running = true
}
override def onDestroy {
super.onDestroy
running = false
}
override def onStartListening {
super.onStartListening
attachService(callback)
@@ -50,6 +50,7 @@ import android.os._
import android.util.Log
import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.job.AclSyncJob
import com.github.shadowsocks.utils._
import scala.collection.mutable.ArrayBuffer
@@ -162,6 +163,10 @@ class ShadowsocksVpnService extends VpnService with BaseService {
handleConnection()
changeState(State.CONNECTED)
if (profile.route != Route.ALL)
AclSyncJob.schedule(profile.route)
notification = new ShadowsocksNotification(this, profile.name)
}
@@ -190,8 +195,14 @@ class ShadowsocksVpnService extends VpnService with BaseService {
def startKcptunDaemon() {
if (profile.kcpcli == null) profile.kcpcli = ""
val host = if (profile.host.contains(":")) {
"[" + profile.host + "]"
} else {
profile.host
}
val cmd = ArrayBuffer(getApplicationInfo.dataDir + "/kcptun"
, "-r", profile.host + ":" + profile.kcpPort
, "-r", host + ":" + profile.kcpPort
, "-l", "127.0.0.1:" + (profile.localPort + 90)
, "--path", protectPath)
try cmd ++= Utils.translateCommandline(profile.kcpcli) catch {
@@ -251,13 +262,7 @@ class ShadowsocksVpnService extends VpnService with BaseService {
if (profile.route != Route.ALL) {
cmd += "--acl"
profile.route match {
case Route.BYPASS_LAN => cmd += (getApplicationInfo.dataDir + "/bypass_lan.acl")
case Route.BYPASS_CHN => cmd += (getApplicationInfo.dataDir + "/bypass_chn.acl")
case Route.BYPASS_LAN_CHN => cmd += (getApplicationInfo.dataDir + "/bypass_lan_chn.acl")
case Route.GFWLIST => cmd += (getApplicationInfo.dataDir + "/gfwlist.acl")
case Route.CHINALIST => cmd += (getApplicationInfo.dataDir + "/chinalist.acl")
}
cmd += getApplicationInfo.dataDir + '/' + profile.route + ".acl"
}
if (TcpFastOpen.sendEnabled) cmd += "--fast-open"
@@ -306,7 +311,7 @@ class ShadowsocksVpnService extends VpnService with BaseService {
val conf = profile.route match {
case Route.BYPASS_CHN | Route.BYPASS_LAN_CHN | Route.GFWLIST => {
ConfigUtils.PDNSD_DIRECT.formatLocal(Locale.ENGLISH, protect, getApplicationInfo.dataDir,
"0.0.0.0", profile.localPort + 53, "114.114.114.114, 119.29.29.29, 1.2.4.8",
"0.0.0.0", profile.localPort + 53, "114.114.114.114, 223.5.5.5, 1.2.4.8",
getBlackList, reject, profile.localPort + 63, reject)
}
case Route.CHINALIST => {
@@ -375,16 +380,8 @@ class ShadowsocksVpnService extends VpnService with BaseService {
builder.addRoute("8.8.0.0", 16)
try {
conn = builder.establish()
if (conn == null) changeState(State.STOPPED, getString(R.string.reboot_required))
} catch {
case ex: Exception =>
ex.printStackTrace()
app.track(ex)
stopRunner(true, ex.getMessage)
return -1
}
conn = builder.establish()
if (conn == null) throw new NullConnectionException
val fd = conn.getFd
@@ -0,0 +1,46 @@
package com.github.shadowsocks.job
import java.io.IOException
import java.net.URL
import java.util.concurrent.TimeUnit
import com.evernote.android.job.Job.{Params, Result}
import com.evernote.android.job.{Job, JobRequest}
import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.utils.CloseUtils._
import com.github.shadowsocks.utils.IOUtils
/**
* @author Mygod
*/
object AclSyncJob {
final val TAG = "AclSyncJob"
def schedule(route: String) = new JobRequest.Builder(AclSyncJob.TAG + ':' + route)
.setExecutionWindow(1, TimeUnit.DAYS.toMillis(28))
.setRequirementsEnforced(true)
.setRequiredNetworkType(JobRequest.NetworkType.UNMETERED)
.setRequiresCharging(true)
.setUpdateCurrent(true)
.build().schedule()
}
class AclSyncJob(route: String) extends Job {
override def onRunJob(params: Params): Result = {
val filename = route + ".acl"
try {
//noinspection JavaAccessorMethodCalledAsEmptyParen
IOUtils.writeString(app.getApplicationInfo.dataDir + '/' + filename, autoClose(
new URL("https://raw.githubusercontent.com/shadowsocks/shadowsocks-android/master/src/main/assets/acl/" +
filename).openConnection().getInputStream())(IOUtils.readString))
Result.SUCCESS
} catch {
case e: IOException =>
e.printStackTrace()
Result.RESCHEDULE
case e: Exception => // unknown failures, probably shouldn't retry
e.printStackTrace()
Result.FAILURE
}
}
}
@@ -0,0 +1,24 @@
package com.github.shadowsocks.job
import android.util.Log
import com.evernote.android.job.JobCreator
/**
* “I create jobs all day long.
* - Donald Trump, 2015
*
* Source: http://www.cnn.com/2015/09/24/politics/donald-trump-marco-rubio-foreign-policy/
*
* @author !Mygod
*/
object DonaldTrump extends JobCreator {
def create(tag: String) = {
val parts = tag.split(":")
parts(0) match {
case AclSyncJob.TAG => new AclSyncJob(parts(1))
case _ =>
Log.w("DonaldTrump", "Unknown job tag: " + tag)
null
}
}
}
@@ -3,7 +3,7 @@ package com.github.shadowsocks.preferences
import android.app.AlertDialog
import android.content.DialogInterface
import eu.chainfire.libsuperuser.Shell
import tk.mygod.preference.EditTextPreferenceDialogFragment
import be.mygod.preference.EditTextPreferenceDialogFragment
import scala.collection.JavaConverters._
@@ -0,0 +1,31 @@
package com.github.shadowsocks.utils
import com.github.shadowsocks.utils.CloseUtils._
import java.io.{FileWriter, InputStream, OutputStream}
/**
* @author Mygod
*/
object IOUtils {
private final val BUFFER_SIZE = 32 * 1024
def copy(in: InputStream, out: OutputStream) {
val buffer = new Array[Byte](BUFFER_SIZE)
while (true) {
val count = in.read(buffer)
if (count >= 0) out.write(buffer, 0, count) else return
}
}
def readString(in: InputStream): String = {
val builder = new StringBuilder()
val buffer = new Array[Byte](BUFFER_SIZE)
while (true) {
val count = in.read(buffer)
if (count >= 0) builder.append(new String(buffer, 0, count)) else return builder.toString()
}
null
}
def writeString(file: String, content: String) = autoClose(new FileWriter(file))(writer => writer.write(content))
}
@@ -54,7 +54,6 @@ import android.view.{Gravity, View, Window}
import android.widget.Toast
import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.{BuildConfig, ShadowsocksRunnerService}
import eu.chainfire.libsuperuser.Shell
import org.xbill.DNS._
import scala.collection.JavaConversions._
@@ -77,8 +76,7 @@ object Utils {
new String(Base64.encode(mdg.digest, 0))
}
def dpToPx(context: Context, dp: Int): Int =
Math.round(dp * (context.getResources.getDisplayMetrics.xdpi / DisplayMetrics.DENSITY_DEFAULT))
def dpToPx(context: Context, dp: Int): Int = Math.round(dp * context.getResources.getDisplayMetrics.density)
/*
* round or floor depending on whether you are using offsets(floor) or
@@ -170,19 +168,6 @@ object Utils {
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
"ndc resolver flushif ${if##*/}; " +
"fi " +
"done; echo done"
// Blocked > 3 seconds
def toggleAirplaneMode(context: Context) = {
val result = Shell.SU.run(FLUSH_DNS)
result != null && !result.isEmpty
}
def resolve(host: String, addrType: Int): Option[String] = {
try {
val lookup = new Lookup(host, addrType)
@@ -259,9 +244,6 @@ object Utils {
}
def startSsService(context: Context) {
val isInstalled = app.settings.getInt(Key.currentVersionCode, -1) == BuildConfig.VERSION_CODE
if (!isInstalled) return
val intent = new Intent(context, classOf[ShadowsocksRunnerService])
context.startService(intent)
}