Compare commits

...
96 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
Max Lv c842f7fd41 Bump version 2016-10-03 19:47:53 +08:00
Max Lv c2f02994f8 Refine DNS server with China List 2016-10-02 19:36:53 +08:00
Max Lv 54247d6b2b Update DNS 2016-09-30 20:48:20 +08:00
Max Lv e7fbfd668d Update config name 2016-09-30 19:43:30 +08:00
Max Lv b6d0af1ec3 Fix #910 2016-09-30 19:28:03 +08:00
Max Lv f58301b80b Bump version 2016-09-30 13:49:16 +08:00
Max Lv 792870b289 Update ACL 2016-09-29 11:53:40 +08:00
Max Lv 3b6eff6d9d Fix #905 2016-09-28 14:36:36 +08:00
Max Lv 528fea136d Clean up 2016-09-28 10:40:16 +08:00
Mygod 5d7e88310b Update dependencies 2016-09-26 19:51:38 +08:00
Max Lv 8c4508843b Refine DNS resolution 2016-09-26 13:54:34 +08:00
Max Lv 79acf14069 Reduce the timeout of default DNS server 2016-09-26 09:48:42 +08:00
Max Lv 5f43500b9f Fix auto-start again 2016-09-26 07:51:54 +08:00
Max Lv 8b4a8a92f2 Bump version 2016-09-25 18:29:41 +08:00
Max Lv 7887d18f6f Clean up 2016-09-25 17:40:53 +08:00
Max Lv fb960d0664 Fix #900 2016-09-25 17:32:16 +08:00
Max Lv 9ed320d33b Fix #901 2016-09-25 17:29:27 +08:00
Max Lv 2599e88a81 Add fake demographics 2016-09-24 11:34:08 +08:00
Max Lv ac0a8c7600 Bump version 2016-09-24 10:59:24 +08:00
81 changed files with 25289 additions and 8940 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.0",
"com.google.android.gms" % "play-services-analytics" % "9.6.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"
)
proguardVersion in Android := "5.2.1"
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="158"
android:versionName="3.1.6">
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
+14
View File
@@ -0,0 +1,14 @@
0.0.0.0/8
10.0.0.0/8
100.64.0.0/10
127.0.0.0/8
169.254.0.0/16
172.16.0.0/12
192.0.0.0/29
192.0.2.0/24
192.88.99.0/24
192.168.0.0/16
198.18.0.0/15
198.51.100.0/24
203.0.113.0/24
224.0.0.0/3
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

-17
View File
@@ -47,23 +47,6 @@
<item>china-list</item>
</string-array>
<string-array name="private_route">
<item>0.0.0.0/8</item>
<item>10.0.0.0/8</item>
<item>100.64.0.0/10</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>
<item>192.88.99.0/24</item>
<item>192.168.0.0/16</item>
<item>198.18.0.0/15</item>
<item>198.51.100.0/24</item>
<item>203.0.113.0/24</item>
<item>224.0.0.0/3</item>
</string-array>
<string-array name="bypass_private_route">
<item>1.0.0.0/8</item>
<item>2.0.0.0/7</item>
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+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,15 +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.ContextThemeWrapper
import android.widget.NumberPicker
import com.github.shadowsocks.R
class NumberPickerPreference(private val context: Context, attrs: AttributeSet = null)
extends DialogPreference(context, attrs) with SummaryPreference {
private[preference] val picker = new NumberPicker(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 = _
{
@@ -19,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 = _
@@ -70,6 +71,7 @@ trait BaseService extends Service {
var callbacksCount: Int = _
lazy val handler = new Handler(getMainLooper)
lazy val restartHanlder = new Handler(getMainLooper)
lazy val protectPath = getApplicationInfo.dataDir + "/protect_path"
private val closeReceiver: BroadcastReceiver = (context: Context, intent: Intent) => {
Toast.makeText(context, R.string.stopping, Toast.LENGTH_SHORT).show()
@@ -140,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
@@ -199,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()
@@ -276,6 +278,7 @@ trait BaseService extends Service {
override def onCreate() {
super.onCreate()
app.refreshContainerHolder
app.updateAssets()
}
// Service of shadowsocks should always be started explicitly
@@ -303,10 +306,11 @@ trait BaseService extends Service {
val default = getString(R.string.black_list)
try {
val container = app.containerHolder.getContainer
val update = container.getString("black_list")
if (update == null || update.isEmpty) default else update
val update = container.getString("black_list_lite")
val list = if (update == null || update.isEmpty) default else update
"exclude = " + list + ";"
} catch {
case ex: Exception => default
case ex: Exception => "exclude = " + default + ";"
}
}
}
@@ -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,24 +1,25 @@
package com.github.shadowsocks
import android.Manifest
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]) {
@@ -35,26 +36,34 @@ 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() {
super.onResume()
val permissionCheck = ContextCompat.checkSelfPermission(this,
Manifest.permission.CAMERA)
android.Manifest.permission.CAMERA)
if (permissionCheck == PackageManager.PERMISSION_GRANTED) {
scannerView.setResultHandler(this) // Register ourselves as a handler for scan results.
scannerView.startCamera() // Start camera on resume
} else {
ActivityCompat.requestPermissions(this,
Array(Manifest.permission.CAMERA), MY_PERMISSIONS_REQUEST_CAMERA)
Array(android.Manifest.permission.CAMERA), MY_PERMISSIONS_REQUEST_CAMERA)
}
}
@@ -67,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,16 +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
import java.util.Locale
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._
@@ -65,9 +63,8 @@ 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 {
def get(c: Context, assetPath: String): Typeface = {
@@ -94,8 +91,6 @@ 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 {
@@ -185,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() {
@@ -200,8 +187,8 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext {
}
override def binderDied {
detachService
crashRecovery
detachService()
app.crashRecovery()
attachService
}
@@ -239,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)
@@ -429,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
@@ -487,7 +411,18 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext {
adView.setAdUnitId("ca-app-pub-9097031975646651/7760346322")
adView.setAdSize(AdSize.SMART_BANNER)
preferences.getView.asInstanceOf[ViewGroup].addView(adView, 1)
adView.loadAd(new AdRequest.Builder().build())
// Demographics
val random = new Random()
val adBuilder = new AdRequest.Builder()
adBuilder.setGender(AdRequest.GENDER_MALE)
val year = 1975 + random.nextInt(40)
val month = 1 + random.nextInt(12)
val day = random.nextInt(28)
adBuilder.setBirthday(new GregorianCalendar(year, month, day).getTime)
// Load Ad
adView.loadAd(adBuilder.build())
} else adView.setVisibility(View.VISIBLE) else if (adView != null) adView.setVisibility(View.GONE)
preferences.setProfile(profile)
@@ -510,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
@@ -71,20 +72,6 @@ class ShadowsocksNatService extends BaseService {
var su: Shell.Interactive = _
def startShadowsocksDaemon() {
if (profile.route != Route.ALL && profile.route != Route.GFWLIST) {
val acl: Array[String] = profile.route match {
case Route.BYPASS_LAN => getResources.getStringArray(R.array.private_route)
case Route.BYPASS_CHN => getResources.getStringArray(R.array.chn_route)
case Route.BYPASS_LAN_CHN =>
getResources.getStringArray(R.array.private_route) ++ getResources.getStringArray(R.array.chn_route)
case Route.CHINALIST =>
Array("[bypass_all]", "[white_list]") ++ getResources.getStringArray(R.array.chn_route)
}
Utils.printToFile(new File(getApplicationInfo.dataDir + "/acl.list"))(p => {
acl.foreach(p.println)
})
}
val conf = if (profile.kcp) {
ConfigUtils
.SHADOWSOCKS.formatLocal(Locale.ENGLISH, "127.0.0.1", profile.localPort + 90, profile.localPort,
@@ -110,10 +97,7 @@ class ShadowsocksNatService extends BaseService {
if (profile.route != Route.ALL) {
cmd += "--acl"
if (profile.route == Route.GFWLIST)
cmd += (getApplicationInfo.dataDir + "/gfwlist.acl")
else
cmd += (getApplicationInfo.dataDir + "/acl.list")
cmd += getApplicationInfo.dataDir + '/' + profile.route + ".acl"
}
if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" "))
@@ -123,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)
@@ -147,11 +137,16 @@ class ShadowsocksNatService extends BaseService {
, "-u"
, "-t" , "10"
, "-b" , "127.0.0.1"
, "-L" , "8.8.8.8:53"
, "-l" , (profile.localPort + 53).toString
, "-P" , getApplicationInfo.dataDir
, "-c" , getApplicationInfo.dataDir + "/ss-tunnel-nat.conf")
cmd += "-L"
if (profile.route == Route.CHINALIST)
cmd += "114.114.114.114:53"
else
cmd += "8.8.8.8:53"
if (profile.auth) cmd += "-A"
if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" "))
@@ -189,22 +184,22 @@ class ShadowsocksNatService extends BaseService {
def startDnsDaemon() {
val reject = if (profile.ipv6) "224.0.0.0/3" else "224.0.0.0/3, ::/0"
val conf = profile.route match {
case Route.BYPASS_CHN | Route.BYPASS_LAN_CHN => {
ConfigUtils.PDNSD_DIRECT.formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir,
"127.0.0.1", profile.localPort + 53, getBlackList, "", profile.localPort + 63, "")
}
case Route.GFWLIST => {
ConfigUtils.PDNSD_UDP.formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir,
"127.0.0.1", profile.localPort + 53, "1.2.4.8, 208.67.222.222", "", profile.localPort + 63, "")
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, "114.114.114.114, 223.5.5.5, 1.2.4.8",
getBlackList, reject, profile.localPort + 63, reject)
}
case Route.CHINALIST => {
ConfigUtils.PDNSD_UDP.formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir,
"127.0.0.1", profile.localPort + 53, "8.8.8.8, 208.67.222.222", "", profile.localPort + 63, "")
ConfigUtils.PDNSD_DIRECT.formatLocal(Locale.ENGLISH, "", getApplicationInfo.dataDir,
"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 _ => {
ConfigUtils.PDNSD_LOCAL.formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir,
"127.0.0.1", profile.localPort + 53, profile.localPort + 63, "")
ConfigUtils.PDNSD_LOCAL.formatLocal(Locale.ENGLISH, "", getApplicationInfo.dataDir,
"127.0.0.1", profile.localPort + 53, profile.localPort + 63, reject)
}
}
@@ -336,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")
}
}
@@ -54,10 +54,12 @@ class ShadowsocksRunnerService extends Service with ServiceBoundContext {
}
override def onServiceConnected() {
handler.postDelayed(() => if (bgService != null) {
if (app.isNatEnabled) startBackgroundService()
else if (VpnService.prepare(ShadowsocksRunnerService.this) == null) startBackgroundService()
stopSelf()
handler.postDelayed(() => {
if (bgService != null) {
if (app.isNatEnabled) startBackgroundService()
else if (VpnService.prepare(ShadowsocksRunnerService.this) == null) startBackgroundService()
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,10 +195,16 @@ 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", getApplicationInfo.dataDir + "/protect_path")
, "--path", protectPath)
try cmd ++= Utils.translateCommandline(profile.kcpcli) catch {
case exc: Exception => throw KcpcliParseException(exc)
}
@@ -226,20 +237,6 @@ class ShadowsocksVpnService extends VpnService with BaseService {
}
def startShadowsocksDaemon() {
if (profile.route != Route.ALL && profile.route != Route.GFWLIST) {
val acl: Array[String] = profile.route match {
case Route.BYPASS_LAN => getResources.getStringArray(R.array.private_route)
case Route.BYPASS_CHN => getResources.getStringArray(R.array.chn_route)
case Route.BYPASS_LAN_CHN =>
getResources.getStringArray(R.array.private_route) ++ getResources.getStringArray(R.array.chn_route)
case Route.CHINALIST =>
Array("[bypass_all]", "[white_list]") ++ getResources.getStringArray(R.array.chn_route)
}
Utils.printToFile(new File(getApplicationInfo.dataDir + "/acl.list"))(p => {
acl.foreach(p.println)
})
}
val conf = if (profile.kcp) {
ConfigUtils
.SHADOWSOCKS.formatLocal(Locale.ENGLISH, "127.0.0.1", profile.localPort + 90, profile.localPort,
@@ -265,10 +262,7 @@ class ShadowsocksVpnService extends VpnService with BaseService {
if (profile.route != Route.ALL) {
cmd += "--acl"
if (profile.route == Route.GFWLIST)
cmd += (getApplicationInfo.dataDir + "/gfwlist.acl")
else
cmd += (getApplicationInfo.dataDir + "/acl.list")
cmd += getApplicationInfo.dataDir + '/' + profile.route + ".acl"
}
if (TcpFastOpen.sendEnabled) cmd += "--fast-open"
@@ -293,13 +287,17 @@ class ShadowsocksVpnService extends VpnService with BaseService {
})
val cmd = ArrayBuffer[String](getApplicationInfo.dataDir + "/ss-tunnel"
, "-V"
, "-u"
, "-t", "10"
, "-b", "127.0.0.1"
, "-L", "8.8.8.8:53"
, "-P", getApplicationInfo.dataDir
, "-c", getApplicationInfo.dataDir + "/ss-tunnel-vpn.conf")
cmd += "-L"
if (profile.route == Route.CHINALIST)
cmd += "114.114.114.114:53"
else
cmd += "8.8.8.8:53"
if (profile.auth) cmd += "-A"
if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" "))
@@ -308,23 +306,22 @@ class ShadowsocksVpnService extends VpnService with BaseService {
}
def startDnsDaemon() {
val ipv6 = if (profile.ipv6) "" else "reject = ::/0;"
val reject = if (profile.ipv6) "224.0.0.0/3" else "224.0.0.0/3, ::/0"
val protect = "protect = \"" + protectPath +"\";"
val conf = profile.route match {
case Route.BYPASS_CHN | Route.BYPASS_LAN_CHN => {
ConfigUtils.PDNSD_DIRECT.formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir,
"0.0.0.0", profile.localPort + 53, getBlackList, ipv6, profile.localPort + 63, ipv6)
}
case Route.GFWLIST => {
ConfigUtils.PDNSD_UDP.formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir,
"0.0.0.0", profile.localPort + 53, "1.2.4.8, 208.67.222.222", ipv6, profile.localPort + 63, ipv6)
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, 223.5.5.5, 1.2.4.8",
getBlackList, reject, profile.localPort + 63, reject)
}
case Route.CHINALIST => {
ConfigUtils.PDNSD_UDP.formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir,
"0.0.0.0", profile.localPort + 53, "8.8.8.8, 208.67.222.222", ipv6, profile.localPort + 63, ipv6)
ConfigUtils.PDNSD_DIRECT.formatLocal(Locale.ENGLISH, protect, getApplicationInfo.dataDir,
"0.0.0.0", profile.localPort + 53, "8.8.8.8, 8.8.4.4, 208.67.222.222",
"", reject, profile.localPort + 63, reject)
}
case _ => {
ConfigUtils.PDNSD_LOCAL.formatLocal(Locale.ENGLISH, getApplicationInfo.dataDir,
"0.0.0.0", profile.localPort + 53, profile.localPort + 63, ipv6)
ConfigUtils.PDNSD_LOCAL.formatLocal(Locale.ENGLISH, protect, getApplicationInfo.dataDir,
"0.0.0.0", profile.localPort + 53, profile.localPort + 63, reject)
}
}
Utils.printToFile(new File(getApplicationInfo.dataDir + "/pdnsd-vpn.conf"))(p => {
@@ -383,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._
@@ -69,11 +69,11 @@ object ConfigUtils {
"""
|global {
| perm_cache = 2048;
| %s
| cache_dir = "%s";
| server_ip = %s;
| server_port = %d;
| query_method = tcp_only;
| run_ipv4 = on;
| min_ttl = 15m;
| max_ttl = 1w;
| timeout = 10;
@@ -84,10 +84,9 @@ object ConfigUtils {
| label = "local";
| ip = 127.0.0.1;
| port = %d;
| %s
| reject = %s;
| reject_policy = negate;
| reject_recursively = on;
| timeout = 5;
|}
|
|rr {
@@ -103,79 +102,36 @@ object ConfigUtils {
"""
|global {
| perm_cache = 2048;
| %s
| cache_dir = "%s";
| server_ip = %s;
| server_port = %d;
| query_method = tcp_only;
| run_ipv4 = on;
| min_ttl = 15m;
| max_ttl = 1w;
| timeout = 10;
| daemon = off;
|}
|
|server {
| label = "china-servers";
| ip = 1.2.4.8, 208.67.222.222, 114.114.114.114;
| timeout = 4;
| exclude = %s;
| policy = included;
| uptest = none;
| preset = on;
| %s
| reject_policy = negate;
| reject_recursively = on;
|}
|
|server {
| label = "local-server";
| ip = 127.0.0.1;
| port = %d;
| %s
| reject_policy = negate;
| reject_recursively = on;
|}
|
|rr {
| name=localhost;
| reverse=on;
| a=127.0.0.1;
| owner=localhost;
| soa=localhost,root.localhost,42,86400,900,86400,86400;
|}
""".stripMargin
val PDNSD_UDP =
"""
|global {
| perm_cache = 2048;
| cache_dir = "%s";
| server_ip = %s;
| server_port = %d;
| query_method = udp_tcp;
| run_ipv4 = on;
| query_method = udp_only;
| min_ttl = 15m;
| max_ttl = 1w;
| timeout = 10;
| daemon = off;
| par_queries = 4;
|}
|
|server {
| label = "remote-servers";
| ip = %s;
| timeout = 5;
| uptest = none;
| preset = on;
| timeout = 3;
| query_method = udp_only;
| %s
| reject_policy = negate;
| policy = included;
| reject = %s;
| reject_policy = fail;
| reject_recursively = on;
|}
|
|server {
| label = "local-server";
| ip = 127.0.0.1;
| query_method = tcp_only;
| port = %d;
| %s
| reject = %s;
| reject_policy = negate;
| reject_recursively = on;
|}
@@ -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)
}