Compare commits

...
30 Commits
Author SHA1 Message Date
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
35 changed files with 437 additions and 213 deletions
+24 -5
View File
@@ -1,10 +1,31 @@
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"*
### 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:
* [ ] 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? ### What did you do?
@@ -14,5 +35,3 @@ Please answer these questions before submitting your issue. Thanks!
### What did you see instead? ### 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
@@ -49,7 +49,7 @@ A [shadowsocks](http://shadowsocks.org) client for Android, written in Scala.
### LICENSE ### LICENSE
Copyright (C) 2016 by Max Lv <<max.c.lv@gmail.com>> 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 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 it under the terms of the GNU General Public License as published by
+3 -1
View File
@@ -22,7 +22,7 @@ shrinkResources in Android := true
typedResources in Android := false typedResources in Android := false
resConfigs in Android := Seq("ru", "zh", "zh-rCN") resConfigs in Android := Seq("ru", "zh-rCN", "zh-rTW")
dexMaxHeap in Android := "4g" dexMaxHeap in Android := "4g"
@@ -34,11 +34,13 @@ libraryDependencies ++= Seq(
"com.android.support" % "design" % "24.2.1", "com.android.support" % "design" % "24.2.1",
"com.android.support" % "gridlayout-v7" % "24.2.1", "com.android.support" % "gridlayout-v7" % "24.2.1",
"com.android.support" % "preference-v14" % "24.2.1", "com.android.support" % "preference-v14" % "24.2.1",
"com.evernote" % "android-job" % "1.1.0",
"com.github.clans" % "fab" % "1.6.4", "com.github.clans" % "fab" % "1.6.4",
"com.github.jorgecastilloprz" % "fabprogresscircle" % "1.01", "com.github.jorgecastilloprz" % "fabprogresscircle" % "1.01",
"com.github.kevinsawicki" % "http-request" % "6.0", "com.github.kevinsawicki" % "http-request" % "6.0",
"com.google.android.gms" % "play-services-ads" % "9.6.1", "com.google.android.gms" % "play-services-ads" % "9.6.1",
"com.google.android.gms" % "play-services-analytics" % "9.6.1", "com.google.android.gms" % "play-services-analytics" % "9.6.1",
"com.google.android.gms" % "play-services-gcm" % "9.6.1",
"com.j256.ormlite" % "ormlite-android" % "5.0", "com.j256.ormlite" % "ormlite-android" % "5.0",
"com.twofortyfouram" % "android-plugin-api-for-locale" % "1.0.2", "com.twofortyfouram" % "android-plugin-api-for-locale" % "1.0.2",
"dnsjava" % "dnsjava" % "2.1.7", "dnsjava" % "dnsjava" % "2.1.7",
+14 -2
View File
@@ -1,7 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" package="com.github.shadowsocks" xmlns:tools="http://schemas.android.com/tools" package="com.github.shadowsocks"
android:versionCode="163" android:versionCode="166"
android:versionName="3.2.3"> android:versionName="3.2.6">
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
@@ -158,6 +158,18 @@
<action android:name="com.twofortyfouram.locale.intent.action.FIRE_SETTING"/> <action android:name="com.twofortyfouram.locale.intent.action.FIRE_SETTING"/>
</intent-filter> </intent-filter>
</receiver> </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"/>
</application> </application>
</manifest> </manifest>
+1 -1
View File
@@ -275,7 +275,7 @@ img {
<body> <body>
<p>A <a href="http://shadowsocks.org">Shadowsocks</a> client for Android, written in Scala.</p> <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 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 <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 it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
+110 -6
View File
@@ -48,12 +48,20 @@
</string-array> </string-array>
<string-array name="private_route"> <string-array name="private_route">
<item>0.0.0.0/8</item>
<item>10.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>127.0.0.0/8</item>
<item>169.254.0.0/16</item> <item>169.254.0.0/16</item>
<item>172.16.0.0/12</item> <item>172.16.0.0/12</item>
<item>192.0.0.0/29</item> <item>192.0.0.0/29</item>
<item>192.0.2.0/24</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>
<string-array name="bypass_private_route"> <string-array name="bypass_private_route">
@@ -66,8 +74,47 @@
<item>16.0.0.0/4</item> <item>16.0.0.0/4</item>
<item>32.0.0.0/3</item> <item>32.0.0.0/3</item>
<item>64.0.0.0/3</item> <item>64.0.0.0/3</item>
<item>96.0.0.0/4</item> <item>96.0.0.0/6</item>
<item>112.0.0.0/5</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>120.0.0.0/6</item>
<item>124.0.0.0/7</item> <item>124.0.0.0/7</item>
<item>126.0.0.0/8</item> <item>126.0.0.0/8</item>
@@ -109,12 +156,69 @@
<item>192.8.0.0/13</item> <item>192.8.0.0/13</item>
<item>192.16.0.0/12</item> <item>192.16.0.0/12</item>
<item>192.32.0.0/11</item> <item>192.32.0.0/11</item>
<item>192.64.0.0/10</item> <item>192.64.0.0/12</item>
<item>192.128.0.0/9</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>193.0.0.0/8</item>
<item>194.0.0.0/7</item> <item>194.0.0.0/7</item>
<item>196.0.0.0/6</item> <item>196.0.0.0/7</item>
<item>200.0.0.0/5</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> <item>208.0.0.0/4</item>
</string-array> </string-array>
</resources> </resources>
+3
View File
@@ -44,5 +44,8 @@
<item name="android:fadeScrollbars">true</item> <item name="android:fadeScrollbars">true</item>
<item name="android:scrollbars">vertical</item> <item name="android:scrollbars">vertical</item>
</style> </style>
<style name="NumberPickerStyle">
<item name="android:textSize">14sp</item>
</style>
</resources> </resources>
+17 -20
View File
@@ -2,40 +2,40 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res/com.github.shadowsocks"> xmlns:app="http://schemas.android.com/apk/res/com.github.shadowsocks">
<tk.mygod.preference.PreferenceCategory <be.mygod.preference.PreferenceCategory
android:title="@string/proxy_cat"> android:title="@string/proxy_cat">
<tk.mygod.preference.EditTextPreference <be.mygod.preference.EditTextPreference
android:key="profileName" android:key="profileName"
android:persistent="false" android:persistent="false"
android:summary="%s" android:summary="%s"
android:title="@string/profile_name"/> android:title="@string/profile_name"/>
<tk.mygod.preference.EditTextPreference <be.mygod.preference.EditTextPreference
android:key="proxy" android:key="proxy"
android:persistent="false" android:persistent="false"
android:summary="%s" android:summary="%s"
android:title="@string/proxy"/> android:title="@string/proxy"/>
<tk.mygod.preference.NumberPickerPreference <be.mygod.preference.NumberPickerPreference
app:min="1" app:min="1"
app:max="65535" app:max="65535"
android:key="remotePortNum" android:key="remotePortNum"
android:persistent="false" android:persistent="false"
android:summary="%d" android:summary="%d"
android:title="@string/remote_port"/> android:title="@string/remote_port"/>
<tk.mygod.preference.NumberPickerPreference <be.mygod.preference.NumberPickerPreference
app:min="1025" app:min="1025"
app:max="65535" app:max="65535"
android:key="localPortNum" android:key="localPortNum"
android:persistent="false" android:persistent="false"
android:summary="%d" android:summary="%d"
android:title="@string/port"/> android:title="@string/port"/>
<tk.mygod.preference.EditTextPreference <be.mygod.preference.EditTextPreference
android:inputType="textPassword" android:inputType="textPassword"
android:key="sitekey" android:key="sitekey"
android:persistent="false" android:persistent="false"
android:summary="%s" android:summary="%s"
android:title="@string/sitekey"/> android:title="@string/sitekey"/>
<android.support.v7.preference.DropDownPreference <DropDownPreference
android:key="encMethod" android:key="encMethod"
android:persistent="false" android:persistent="false"
android:entries="@array/enc_method_entry" android:entries="@array/enc_method_entry"
@@ -48,12 +48,12 @@
android:summary="@string/onetime_auth_summary" android:summary="@string/onetime_auth_summary"
android:title="@string/onetime_auth"/> 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:title="@string/feature_cat">
<android.support.v7.preference.DropDownPreference <DropDownPreference
android:key="route" android:key="route"
android:persistent="false" android:persistent="false"
android:entries="@array/route_entry" android:entries="@array/route_entry"
@@ -76,9 +76,9 @@
android:summary="@string/udp_dns_summary" android:summary="@string/udp_dns_summary"
android:title="@string/udp_dns"/> 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"> android:title="@string/kcp">
<SwitchPreference <SwitchPreference
@@ -87,7 +87,7 @@
android:summary="@string/kcp_summary" android:summary="@string/kcp_summary"
android:title="@string/kcp"/> android:title="@string/kcp"/>
<tk.mygod.preference.NumberPickerPreference <be.mygod.preference.NumberPickerPreference
app:min="1" app:min="1"
app:max="65535" app:max="65535"
android:key="kcpPort" android:key="kcpPort"
@@ -95,15 +95,15 @@
android:summary="@string/kcp_port_summary" android:summary="@string/kcp_port_summary"
android:title="@string/kcp_port"/> android:title="@string/kcp_port"/>
<tk.mygod.preference.EditTextPreference <be.mygod.preference.EditTextPreference
android:key="kcpcli" android:key="kcpcli"
android:persistent="false" android:persistent="false"
android:summary="%s" android:summary="%s"
android:title="@string/kcpcli"/> 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 <SwitchPreference
android:key="isAutoConnect" android:key="isAutoConnect"
@@ -121,12 +121,9 @@
android:summary="@string/nat_summary"/> android:summary="@string/nat_summary"/>
<Preference android:key="recovery" android:title="@string/recovery" <Preference android:key="recovery" android:title="@string/recovery"
android:summary="@string/recovery_summary"/> 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"/> <Preference android:key="about" android:title="@string/about"/>
</tk.mygod.preference.PreferenceCategory> </be.mygod.preference.PreferenceCategory>
</PreferenceScreen> </PreferenceScreen>
@@ -1,4 +1,4 @@
package tk.mygod.preference package be.mygod.preference
import android.content.Context import android.content.Context
import android.support.v7.preference.{EditTextPreference => Parent} import android.support.v7.preference.{EditTextPreference => Parent}
@@ -1,4 +1,4 @@
package tk.mygod.preference package be.mygod.preference
import android.content.Context import android.content.Context
import android.support.v14.preference.PreferenceDialogFragment 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.Context
import android.content.res.TypedArray import android.content.res.TypedArray
import android.support.v7.preference.DialogPreference import android.support.v7.preference.DialogPreference
import android.util.AttributeSet import android.util.AttributeSet
import android.view.View import android.view.ContextThemeWrapper
import android.view.ViewGroup.LayoutParams
import android.widget.NumberPicker import android.widget.NumberPicker
import android.widget.EditText
import com.github.shadowsocks.R 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) class NumberPickerPreference(private val context: Context, attrs: AttributeSet = null)
extends DialogPreference(context, attrs) with SummaryPreference { extends DialogPreference(context, attrs) with SummaryPreference {
private[preference] val picker = new MyNumberPicker(context) private[preference] val picker = new NumberPicker(new ContextThemeWrapper(context, R.style.NumberPickerStyle))
private var value: Int = _ private var value: Int = _
{ {
@@ -1,4 +1,4 @@
package tk.mygod.preference package be.mygod.preference
import android.content.Context import android.content.Context
import android.support.v14.preference.PreferenceDialogFragment import android.support.v14.preference.PreferenceDialogFragment
@@ -1,4 +1,4 @@
package tk.mygod.preference package be.mygod.preference
import android.content.Context import android.content.Context
import android.support.v7.preference.{PreferenceViewHolder, PreferenceCategory => Base} import android.support.v7.preference.{PreferenceViewHolder, PreferenceCategory => Base}
@@ -1,4 +1,4 @@
package tk.mygod.preference package be.mygod.preference
import android.app.DialogFragment import android.app.DialogFragment
import android.os.Bundle import android.os.Bundle
@@ -1,4 +1,4 @@
package tk.mygod.preference package be.mygod.preference
import java.lang.reflect.Field import java.lang.reflect.Field
import java.util.List import java.util.List
@@ -1,4 +1,4 @@
package tk.mygod.preference package be.mygod.preference
import android.support.v7.preference.Preference import android.support.v7.preference.Preference
@@ -50,10 +50,10 @@ import android.text.TextUtils
import android.util.Log import android.util.Log
import android.widget.Toast import android.widget.Toast
import com.github.kevinsawicki.http.HttpRequest 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.ShadowsocksApplication.app
import com.github.shadowsocks.aidl.{IShadowsocksService, IShadowsocksServiceCallback}
import com.github.shadowsocks.database.Profile import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.utils._
trait BaseService extends Service { trait BaseService extends Service {
@@ -62,6 +62,7 @@ trait BaseService extends Service {
case class NameNotResolvedException() extends IOException case class NameNotResolvedException() extends IOException
case class KcpcliParseException(cause: Throwable) extends Exception(cause) case class KcpcliParseException(cause: Throwable) extends Exception(cause)
case class NullConnectionException() extends NullPointerException
var timer: Timer = _ var timer: Timer = _
var trafficMonitorThread: TrafficMonitorThread = _ var trafficMonitorThread: TrafficMonitorThread = _
@@ -141,8 +142,7 @@ trait BaseService extends Service {
} }
def checkProfile(profile: Profile) = if (TextUtils.isEmpty(profile.host) || TextUtils.isEmpty(profile.password)) { def checkProfile(profile: Profile) = if (TextUtils.isEmpty(profile.host) || TextUtils.isEmpty(profile.password)) {
changeState(State.STOPPED, getString(R.string.proxy_empty)) stopRunner(true, getString(R.string.proxy_empty))
stopRunner(true)
false false
} else true } else true
@@ -200,6 +200,7 @@ trait BaseService extends Service {
case _: NameNotResolvedException => stopRunner(true, getString(R.string.invalid_server)) case _: NameNotResolvedException => stopRunner(true, getString(R.string.invalid_server))
case exc: KcpcliParseException => case exc: KcpcliParseException =>
stopRunner(true, getString(R.string.service_failed) + ": " + exc.cause.getMessage) stopRunner(true, getString(R.string.service_failed) + ": " + exc.cause.getMessage)
case _: NullConnectionException => stopRunner(true, getString(R.string.reboot_required))
case exc: Throwable => case exc: Throwable =>
stopRunner(true, getString(R.string.service_failed) + ": " + exc.getMessage) stopRunner(true, getString(R.string.service_failed) + ": " + exc.getMessage)
exc.printStackTrace() exc.printStackTrace()
@@ -277,6 +278,7 @@ trait BaseService extends Service {
override def onCreate() { override def onCreate() {
super.onCreate() super.onCreate()
app.refreshContainerHolder app.refreshContainerHolder
app.updateAssets()
} }
// Service of shadowsocks should always be started explicitly // Service of shadowsocks should always be started explicitly
@@ -38,17 +38,14 @@
*/ */
package com.github.shadowsocks package com.github.shadowsocks
import java.io.{FileOutputStream, IOException, InputStream, OutputStream}
import java.lang.System.currentTimeMillis import java.lang.System.currentTimeMillis
import java.net.{HttpURLConnection, URL} import java.net.{HttpURLConnection, URL}
import java.util.Date import java.util
import java.util.Hashtable import java.util.{GregorianCalendar, Locale}
import java.util.Locale
import android.app.backup.BackupManager import android.app.backup.BackupManager
import android.app.{Activity, ProgressDialog} import android.app.{Activity, ProgressDialog}
import android.content._ import android.content._
import android.content.res.AssetManager
import android.graphics.Typeface import android.graphics.Typeface
import android.net.VpnService import android.net.VpnService
import android.os._ import android.os._
@@ -89,15 +86,13 @@ object Typefaces {
} }
private final val TAG = "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 { object Shadowsocks {
// Constants // Constants
private final val TAG = "Shadowsocks" private final val TAG = "Shadowsocks"
private final val REQUEST_CONNECT = 1 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 { class Shadowsocks extends AppCompatActivity with ServiceBoundContext {
@@ -187,14 +182,6 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext {
snackbar.setAction(R.string.switch_to_vpn, (_ => preferences.natSwitch.setChecked(false)): View.OnClickListener) snackbar.setAction(R.string.switch_to_vpn, (_ => preferences.natSwitch.setChecked(false)): View.OnClickListener)
snackbar.show 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() { override def onServiceDisconnected() {
@@ -241,53 +228,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() { def crashRecovery() {
val cmd = new ArrayBuffer[String]() val cmd = new ArrayBuffer[String]()
@@ -497,7 +437,7 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext {
val year = 1975 + random.nextInt(40) val year = 1975 + random.nextInt(40)
val month = 1 + random.nextInt(12) val month = 1 + random.nextInt(12)
val day = random.nextInt(28) val day = random.nextInt(28)
adBuilder.setBirthday(new Date(year, month, day)) adBuilder.setBirthday(new GregorianCalendar(year, month, day).getTime)
// Load Ad // Load Ad
adView.loadAd(adBuilder.build()) adView.loadAd(adBuilder.build())
@@ -526,14 +466,7 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext {
def reset() { def reset() {
crashRecovery() crashRecovery()
copyAssets(System.getABI) app.copyAssets()
copyAssets("acl")
val ab = new ArrayBuffer[String]
for (executable <- EXECUTABLES) {
ab.append("chmod 755 " + getApplicationInfo.dataDir + "/" + executable)
}
Shell.SH.run(ab.toArray)
} }
def recovery() { def recovery() {
@@ -545,15 +478,6 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext {
} }
} }
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)
h.sendEmptyMessage(0)
}
}
override def onActivityResult(requestCode: Int, resultCode: Int, data: Intent) = resultCode match { override def onActivityResult(requestCode: Int, resultCode: Int, data: Intent) = resultCode match {
case Activity.RESULT_OK => case Activity.RESULT_OK =>
serviceLoad() serviceLoad()
@@ -39,21 +39,41 @@
package com.github.shadowsocks package com.github.shadowsocks
import java.io.{FileOutputStream, IOException}
import java.util import java.util
import java.util.Locale
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import android.annotation.SuppressLint
import android.app.Application import android.app.Application
import android.content.res.Configuration
import android.os.{Build, LocaleList}
import android.preference.PreferenceManager import android.preference.PreferenceManager
import android.support.v7.app.AppCompatDelegate 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.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.analytics.{GoogleAnalytics, HitBuilders, StandardExceptionParser}
import com.google.android.gms.common.api.ResultCallback import com.google.android.gms.common.api.ResultCallback
import com.google.android.gms.tagmanager.{ContainerHolder, TagManager} import com.google.android.gms.tagmanager.{ContainerHolder, TagManager}
import com.j256.ormlite.logger.LocalLog import com.j256.ormlite.logger.LocalLog
import eu.chainfire.libsuperuser.Shell
object ShadowsocksApplication { object ShadowsocksApplication {
var app: 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 { class ShadowsocksApplication extends Application {
@@ -88,10 +108,62 @@ class ShadowsocksApplication extends Application {
profileManager.getProfile(id) getOrElse profileManager.createProfile() 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() { override def onCreate() {
if (!BuildConfig.DEBUG) java.lang.System.setProperty(LocalLog.LOCAL_LOG_LEVEL_PROPERTY, "ERROR")
app = this app = this
if (!BuildConfig.DEBUG) java.lang.System.setProperty(LocalLog.LOCAL_LOG_LEVEL_PROPERTY, "ERROR")
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true) AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
checkChineseLocale(getResources.getConfiguration)
val tm = TagManager.getInstance(this) val tm = TagManager.getInstance(this)
val pending = tm.loadContainerPreferNonDefault("GTM-NT8WS8", R.raw.gtm_default_container) val pending = tm.loadContainerPreferNonDefault("GTM-NT8WS8", R.raw.gtm_default_container)
val callback = new ResultCallback[ContainerHolder] { val callback = new ResultCallback[ContainerHolder] {
@@ -111,6 +183,7 @@ class ShadowsocksApplication extends Application {
} }
} }
pending.setResultCallback(callback, 2, TimeUnit.SECONDS) pending.setResultCallback(callback, 2, TimeUnit.SECONDS)
JobManager.create(this).addJobCreator(DonaldTrump)
TcpFastOpen.enabled(settings.getBoolean(Key.tfo, TcpFastOpen.sendEnabled)) TcpFastOpen.enabled(settings.getBoolean(Key.tfo, TcpFastOpen.sendEnabled))
} }
@@ -119,4 +192,27 @@ class ShadowsocksApplication extends Application {
val holder = app.containerHolder val holder = app.containerHolder
if (holder != null) holder.refresh() 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 copyAssets() {
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 android.util.Log
import com.github.shadowsocks.ShadowsocksApplication.app import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.database.Profile import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.job.AclSyncJob
import com.github.shadowsocks.utils._ import com.github.shadowsocks.utils._
import eu.chainfire.libsuperuser.Shell import eu.chainfire.libsuperuser.Shell
@@ -96,13 +97,7 @@ class ShadowsocksNatService extends BaseService {
if (profile.route != Route.ALL) { if (profile.route != Route.ALL) {
cmd += "--acl" cmd += "--acl"
profile.route match { cmd += getApplicationInfo.dataDir + '/' + profile.route + ".acl"
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")
}
} }
if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" ")) if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" "))
@@ -112,8 +107,14 @@ class ShadowsocksNatService extends BaseService {
def startKcptunDaemon() { def startKcptunDaemon() {
if (profile.kcpcli == null) profile.kcpcli = "" if (profile.kcpcli == null) profile.kcpcli = ""
val host = if (profile.host.contains(":")) {
"[" + profile.host + "]"
} else {
profile.host
}
val cmd = ArrayBuffer[String](getApplicationInfo.dataDir + "/kcptun" val cmd = ArrayBuffer[String](getApplicationInfo.dataDir + "/kcptun"
, "-r", profile.host + ":" + profile.kcpPort , "-r", host + ":" + profile.kcpPort
, "-l", "127.0.0.1:" + (profile.localPort + 90)) , "-l", "127.0.0.1:" + (profile.localPort + 90))
try cmd ++= Utils.translateCommandline(profile.kcpcli) catch { try cmd ++= Utils.translateCommandline(profile.kcpcli) catch {
case exc: Exception => throw KcpcliParseException(exc) case exc: Exception => throw KcpcliParseException(exc)
@@ -330,8 +331,9 @@ class ShadowsocksNatService extends BaseService {
} }
handleConnection() handleConnection()
// Set DNS
su.addCommand(Utils.FLUSH_DNS) AclSyncJob.schedule(profile.route)
changeState(State.CONNECTED) changeState(State.CONNECTED)
notification = new ShadowsocksNotification(this, profile.name, true) notification = new ShadowsocksNotification(this, profile.name, true)
} }
@@ -15,7 +15,7 @@ import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.database.Profile import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.preferences.KcpCliPreferenceDialogFragment import com.github.shadowsocks.preferences.KcpCliPreferenceDialogFragment
import com.github.shadowsocks.utils.{Key, TcpFastOpen, Utils} import com.github.shadowsocks.utils.{Key, TcpFastOpen, Utils}
import tk.mygod.preference._ import be.mygod.preference._
object ShadowsocksSettings { object ShadowsocksSettings {
// Constants // Constants
@@ -170,12 +170,6 @@ class ShadowsocksSettings extends PreferenceFragment with OnSharedPreferenceChan
true true
}) })
findPreference("flush_dnscache").setOnPreferenceClickListener(_ => {
app.track(TAG, "flush_dnscache")
activity.flushDnsCache()
true
})
findPreference("about").setOnPreferenceClickListener((preference: Preference) => { findPreference("about").setOnPreferenceClickListener((preference: Preference) => {
app.track(TAG, "about") app.track(TAG, "about")
val web = new WebView(activity) val web = new WebView(activity)
@@ -50,6 +50,7 @@ import android.os._
import android.util.Log import android.util.Log
import com.github.shadowsocks.ShadowsocksApplication.app import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.database.Profile import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.job.AclSyncJob
import com.github.shadowsocks.utils._ import com.github.shadowsocks.utils._
import scala.collection.mutable.ArrayBuffer import scala.collection.mutable.ArrayBuffer
@@ -162,6 +163,9 @@ class ShadowsocksVpnService extends VpnService with BaseService {
handleConnection() handleConnection()
changeState(State.CONNECTED) changeState(State.CONNECTED)
AclSyncJob.schedule(profile.route)
notification = new ShadowsocksNotification(this, profile.name) notification = new ShadowsocksNotification(this, profile.name)
} }
@@ -190,8 +194,14 @@ class ShadowsocksVpnService extends VpnService with BaseService {
def startKcptunDaemon() { def startKcptunDaemon() {
if (profile.kcpcli == null) profile.kcpcli = "" if (profile.kcpcli == null) profile.kcpcli = ""
val host = if (profile.host.contains(":")) {
"[" + profile.host + "]"
} else {
profile.host
}
val cmd = ArrayBuffer(getApplicationInfo.dataDir + "/kcptun" val cmd = ArrayBuffer(getApplicationInfo.dataDir + "/kcptun"
, "-r", profile.host + ":" + profile.kcpPort , "-r", host + ":" + profile.kcpPort
, "-l", "127.0.0.1:" + (profile.localPort + 90) , "-l", "127.0.0.1:" + (profile.localPort + 90)
, "--path", protectPath) , "--path", protectPath)
try cmd ++= Utils.translateCommandline(profile.kcpcli) catch { try cmd ++= Utils.translateCommandline(profile.kcpcli) catch {
@@ -251,13 +261,7 @@ class ShadowsocksVpnService extends VpnService with BaseService {
if (profile.route != Route.ALL) { if (profile.route != Route.ALL) {
cmd += "--acl" cmd += "--acl"
profile.route match { cmd += getApplicationInfo.dataDir + '/' + profile.route + ".acl"
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")
}
} }
if (TcpFastOpen.sendEnabled) cmd += "--fast-open" if (TcpFastOpen.sendEnabled) cmd += "--fast-open"
@@ -375,16 +379,8 @@ class ShadowsocksVpnService extends VpnService with BaseService {
builder.addRoute("8.8.0.0", 16) builder.addRoute("8.8.0.0", 16)
try { conn = builder.establish()
conn = builder.establish() if (conn == null) throw new NullConnectionException
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
}
val fd = conn.getFd 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.app.AlertDialog
import android.content.DialogInterface import android.content.DialogInterface
import eu.chainfire.libsuperuser.Shell import eu.chainfire.libsuperuser.Shell
import tk.mygod.preference.EditTextPreferenceDialogFragment import be.mygod.preference.EditTextPreferenceDialogFragment
import scala.collection.JavaConverters._ 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 android.widget.Toast
import com.github.shadowsocks.ShadowsocksApplication.app import com.github.shadowsocks.ShadowsocksApplication.app
import com.github.shadowsocks.{BuildConfig, ShadowsocksRunnerService} import com.github.shadowsocks.{BuildConfig, ShadowsocksRunnerService}
import eu.chainfire.libsuperuser.Shell
import org.xbill.DNS._ import org.xbill.DNS._
import scala.collection.JavaConversions._ import scala.collection.JavaConversions._
@@ -77,8 +76,7 @@ object Utils {
new String(Base64.encode(mdg.digest, 0)) new String(Base64.encode(mdg.digest, 0))
} }
def dpToPx(context: Context, dp: Int): Int = def dpToPx(context: Context, dp: Int): Int = Math.round(dp * context.getResources.getDisplayMetrics.density)
Math.round(dp * (context.getResources.getDisplayMetrics.xdpi / DisplayMetrics.DENSITY_DEFAULT))
/* /*
* round or floor depending on whether you are using offsets(floor) or * round or floor depending on whether you are using offsets(floor) or
@@ -170,19 +168,6 @@ object Utils {
result 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] = { def resolve(host: String, addrType: Int): Option[String] = {
try { try {
val lookup = new Lookup(host, addrType) val lookup = new Lookup(host, addrType)
@@ -259,9 +244,6 @@ object Utils {
} }
def startSsService(context: Context) { 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]) val intent = new Intent(context, classOf[ShadowsocksRunnerService])
context.startService(intent) context.startService(intent)
} }