Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
com.ccwangluo.accelerator
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
sheteng
com.ccwangluo.accelerator
Commits
dbb137c5
Commit
dbb137c5
authored
Dec 16, 2016
by
Mygod
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Initial redesign draft for #762
parent
7eddf8be
Changes
34
Hide whitespace changes
Inline
Side-by-side
Showing
34 changed files
with
1069 additions
and
729 deletions
+1069
-729
build.sbt
build.sbt
+2
-1
src/main/AndroidManifest.xml
src/main/AndroidManifest.xml
+19
-25
src/main/java/com/github/shadowsocks/DialogFragment.java
src/main/java/com/github/shadowsocks/DialogFragment.java
+15
-0
src/main/res/drawable/ic_action_delete.xml
src/main/res/drawable/ic_action_delete.xml
+9
-0
src/main/res/drawable/ic_action_done.xml
src/main/res/drawable/ic_action_done.xml
+9
-0
src/main/res/drawable/ic_image_edit.xml
src/main/res/drawable/ic_image_edit.xml
+9
-0
src/main/res/layout/layout_global_settings.xml
src/main/res/layout/layout_global_settings.xml
+12
-0
src/main/res/layout/layout_main.xml
src/main/res/layout/layout_main.xml
+76
-72
src/main/res/layout/layout_profile_config.xml
src/main/res/layout/layout_profile_config.xml
+12
-0
src/main/res/layout/layout_profiles.xml
src/main/res/layout/layout_profiles.xml
+9
-64
src/main/res/layout/layout_profiles_item.xml
src/main/res/layout/layout_profiles_item.xml
+46
-36
src/main/res/menu/profile_config_menu.xml
src/main/res/menu/profile_config_menu.xml
+32
-0
src/main/res/menu/profile_manager_menu.xml
src/main/res/menu/profile_manager_menu.xml
+20
-0
src/main/res/values-v21/dimen.xml
src/main/res/values-v21/dimen.xml
+1
-1
src/main/res/values/colors.xml
src/main/res/values/colors.xml
+2
-1
src/main/res/values/dimen.xml
src/main/res/values/dimen.xml
+1
-1
src/main/res/values/styles.xml
src/main/res/values/styles.xml
+2
-2
src/main/res/xml/pref_global.xml
src/main/res/xml/pref_global.xml
+16
-0
src/main/res/xml/pref_profile.xml
src/main/res/xml/pref_profile.xml
+1
-40
src/main/scala/com/github/shadowsocks/AppManager.scala
src/main/scala/com/github/shadowsocks/AppManager.scala
+11
-24
src/main/scala/com/github/shadowsocks/GlobalConfigFragment.scala
...n/scala/com/github/shadowsocks/GlobalConfigFragment.scala
+69
-0
src/main/scala/com/github/shadowsocks/GlobalSettingsFragment.scala
...scala/com/github/shadowsocks/GlobalSettingsFragment.scala
+14
-0
src/main/scala/com/github/shadowsocks/MainActivity.scala
src/main/scala/com/github/shadowsocks/MainActivity.scala
+152
-167
src/main/scala/com/github/shadowsocks/ProfileConfigActivity.scala
.../scala/com/github/shadowsocks/ProfileConfigActivity.scala
+20
-0
src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala
.../scala/com/github/shadowsocks/ProfileConfigFragment.scala
+103
-0
src/main/scala/com/github/shadowsocks/ProfilesFragment.scala
src/main/scala/com/github/shadowsocks/ProfilesFragment.scala
+270
-0
src/main/scala/com/github/shadowsocks/QRCodeDialog.scala
src/main/scala/com/github/shadowsocks/QRCodeDialog.scala
+41
-0
src/main/scala/com/github/shadowsocks/ServiceBoundContext.scala
...in/scala/com/github/shadowsocks/ServiceBoundContext.scala
+2
-2
src/main/scala/com/github/shadowsocks/ShadowsocksNotification.scala
...cala/com/github/shadowsocks/ShadowsocksNotification.scala
+1
-1
src/main/scala/com/github/shadowsocks/ShadowsocksSettings.scala
...in/scala/com/github/shadowsocks/ShadowsocksSettings.scala
+0
-286
src/main/scala/com/github/shadowsocks/ToolbarFragment.scala
src/main/scala/com/github/shadowsocks/ToolbarFragment.scala
+17
-0
src/main/scala/com/github/shadowsocks/database/Profile.scala
src/main/scala/com/github/shadowsocks/database/Profile.scala
+40
-1
src/main/scala/com/github/shadowsocks/utils/Constants.scala
src/main/scala/com/github/shadowsocks/utils/Constants.scala
+8
-5
src/main/scala/com/github/shadowsocks/utils/Typefaces.scala
src/main/scala/com/github/shadowsocks/utils/Typefaces.scala
+28
-0
No files found.
build.sbt
View file @
dbb137c5
...
...
@@ -31,17 +31,18 @@ resConfigs := Seq("ja", "ru", "zh-rCN", "zh-rTW")
resolvers
+=
Resolver
.
jcenterRepo
libraryDependencies
++=
"com.android.support"
%
"cardview-v7"
%
"25.1.0"
::
"com.android.support"
%
"design"
%
"25.1.0"
::
"com.android.support"
%
"gridlayout-v7"
%
"25.1.0"
::
"com.android.support"
%
"preference-v14"
%
"25.1.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"
%
"10.0.1"
::
"com.google.android.gms"
%
"play-services-analytics"
%
"10.0.1"
::
"com.google.android.gms"
%
"play-services-gcm"
%
"10.0.1"
::
"com.j256.ormlite"
%
"ormlite-android"
%
"5.0"
::
"com.mikepenz"
%
"materialdrawer"
%
"5.8.1"
::
"com.twofortyfouram"
%
"android-plugin-api-for-locale"
%
"1.0.2"
::
"dnsjava"
%
"dnsjava"
%
"2.1.7"
::
"eu.chainfire"
%
"libsuperuser"
%
"1.0.0.201608240809"
::
...
...
src/main/AndroidManifest.xml
View file @
dbb137c5
...
...
@@ -38,7 +38,7 @@
android:value=
"AEdPqrEAAAAI_zVxZthz2HDuz9toTvkYvL0L5GA-OjeUIfBeXg"
/>
<activity
android:name=
".
Shadowsocks
"
android:name=
".
MainActivity
"
android:label=
"@string/app_name"
android:launchMode=
"singleTask"
>
<intent-filter>
...
...
@@ -49,28 +49,6 @@
<intent-filter>
<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.Translucent.NoTitleBar"
android:excludeFromRecents=
"true"
android:taskAffinity=
""
android:launchMode=
"singleTask"
>
</activity>
<activity
android:name=
".ProfileManagerActivity"
android:label=
"@string/profiles"
android:parentActivityName=
".Shadowsocks"
android:excludeFromRecents=
"true"
android:launchMode=
"singleTask"
>
<intent-filter>
<action
android:name=
"com.github.shadowsocks.ProfileManagerActivity"
/>
<category
android:name=
"android.intent.category.DEFAULT"
/>
</intent-filter>
<intent-filter>
<action
android:name=
"android.intent.action.VIEW"
/>
<category
android:name=
"android.intent.category.DEFAULT"
/>
...
...
@@ -82,18 +60,34 @@
<category
android:name=
"android.intent.category.DEFAULT"
/>
<data
android:scheme=
"ss"
/>
</intent-filter>
<meta-data
android:name=
"android.app.shortcuts"
android:resource=
"@xml/shortcuts"
/>
</activity>
<activity
android:name=
".ProfileConfigActivity"
android:excludeFromRecents=
"true"
android:label=
"Profile config"
android:launchMode=
"singleTask"
android:parentActivityName=
".MainActivity"
/>
<activity
android:name=
".ShadowsocksRunnerActivity"
android:theme=
"@android:style/Theme.Translucent.NoTitleBar"
android:excludeFromRecents=
"true"
android:taskAffinity=
""
android:launchMode=
"singleTask"
/>
<activity
android:name=
".ScannerActivity"
android:label=
"@string/add_profile_methods_scan_qr_code"
android:parentActivityName=
".
ProfileManager
Activity"
android:parentActivityName=
".
Main
Activity"
android:excludeFromRecents=
"true"
/>
<activity
android:name=
".AppManager"
android:label=
"@string/proxied_apps"
android:parentActivityName=
".
Shadowsocks
"
android:parentActivityName=
".
ProfileConfigActivity
"
android:excludeFromRecents=
"true"
android:launchMode=
"singleTask"
/>
...
...
src/main/java/com/github/shadowsocks/DialogFragment.java
0 → 100644
View file @
dbb137c5
package
com.github.shadowsocks
;
import
android.app.Activity
;
/**
* This helper class is used to solve Java-Scala interop problem. Use onAttach(Context) and remove this class
* when minSdkVersion >= 23.
*
* @author Mygod
*/
public
class
DialogFragment
extends
android
.
app
.
DialogFragment
{
protected
void
superOnAttach
(
Activity
activity
)
{
super
.
onAttach
(
activity
);
}
}
src/main/res/drawable/ic_action_delete.xml
0 → 100644
View file @
dbb137c5
<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=
"M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"
/>
</vector>
src/main/res/drawable/ic_action_done.xml
0 → 100644
View file @
dbb137c5
<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=
"M9,16.2L4.8,12l-1.4,1.4L9,19 21,7l-1.4,-1.4L9,16.2z"
/>
</vector>
src/main/res/drawable/ic_
social_share
.xml
→
src/main/res/drawable/ic_
image_edit
.xml
View file @
dbb137c5
...
...
@@ -5,5 +5,5 @@
android:viewportHeight=
"24.0"
>
<path
android:fillColor=
"#FF000000"
android:pathData=
"M
18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92
z"
/>
android:pathData=
"M
3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83
z"
/>
</vector>
src/main/res/layout/layout_global_settings.xml
0 → 100644
View file @
dbb137c5
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android=
"http://schemas.android.com/apk/res/android"
android:orientation=
"vertical"
android:layout_width=
"match_parent"
android:layout_height=
"match_parent"
>
<include
layout=
"@layout/toolbar_light_dark"
/>
<fragment
android:id=
"@+id/content"
class=
"com.github.shadowsocks.GlobalConfigFragment"
android:layout_width=
"match_parent"
android:layout_height=
"0dp"
android:layout_weight=
"1"
/>
</LinearLayout>
src/main/res/layout/layout_main.xml
View file @
dbb137c5
...
...
@@ -3,83 +3,87 @@
xmlns:app=
"http://schemas.android.com/apk/res-auto"
xmlns:tools=
"http://schemas.android.com/tools"
android:layout_width=
"match_parent"
android:layout_height=
"match_parent"
tools:ignore=
"UnknownIdInLayout"
android:clipChildren=
"false"
>
<LinearLayout
android:id=
"@+id/title_bar"
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
android:orientation=
"vertical"
android:layout_below=
"@+id/toolbar"
android:animateLayoutChanges=
"true"
>
<include
layout=
"@layout/toolbar_light_dark"
/>
<LinearLayout
android:id=
"@+id/stat"
android:elevation=
"4dp"
android:visibility=
"gone"
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
android:minHeight=
"?android:attr/listPreferredItemHeight"
android:gravity=
"center_vertical"
android:paddingStart=
"16dip"
android:paddingEnd=
"16dip"
android:focusable=
"true"
android:paddingTop=
"16dip"
android:paddingBottom=
"16dip"
android:background=
"@drawable/background_stat"
android:orientation=
"vertical"
android:nextFocusRight=
"@+id/fab"
>
<LinearLayout
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
>
<TextView
android:text=
"@string/stat"
android:textSize=
"16sp"
android:textColor=
"?android:attr/textColorPrimary"
android:ellipsize=
"marquee"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
/>
<TextView
android:id=
"@+id/connection_test"
android:gravity=
"end"
android:textSize=
"14sp"
android:textColor=
"?android:attr/textColorSecondary"
android:layout_width=
"0dp"
android:layout_height=
"wrap_content"
android:layout_weight=
"1"
/>
</LinearLayout>
<android.support.v7.widget.GridLayout
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
android:paddingEnd=
"72dip"
app:columnCount=
"4"
>
<TextView
app:layout_column=
"0"
style=
"@style/TextAppearance.AppCompat.Body2"
android:ellipsize=
"marquee"
android:text=
"@string/sent"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:layout_marginEnd=
"24dp"
android:gravity=
"end"
/>
<TextView
app:layout_column=
"1"
android:id=
"@+id/tx"
android:textSize=
"14sp"
android:textColor=
"?android:attr/textColorSecondary"
android:ellipsize=
"marquee"
android:layout_width=
"0dp"
android:layout_height=
"wrap_content"
app:layout_columnWeight=
"1"
app:layout_gravity=
"fill_horizontal"
/>
<TextView
app:layout_column=
"2"
android:textSize=
"14sp"
android:textColor=
"?android:attr/textColorSecondary"
android:ellipsize=
"marquee"
android:text=
"▲"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
/>
<TextView
app:layout_column=
"3"
android:id=
"@+id/txRate"
android:textSize=
"14sp"
android:textColor=
"?android:attr/textColorSecondary"
android:ellipsize=
"marquee"
android:layout_width=
"0dp"
android:layout_height=
"wrap_content"
app:layout_columnWeight=
"1"
app:layout_gravity=
"fill_horizontal"
android:gravity=
"end"
/>
<TextView
app:layout_row=
"1"
app:layout_column=
"0"
style=
"@style/TextAppearance.AppCompat.Body2"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:layout_marginEnd=
"24dp"
android:gravity=
"end"
android:ellipsize=
"marquee"
android:text=
"@string/received"
/>
<TextView
app:layout_row=
"1"
app:layout_column=
"1"
android:id=
"@+id/rx"
android:textSize=
"14sp"
android:textColor=
"?android:attr/textColorSecondary"
android:ellipsize=
"marquee"
android:layout_width=
"0dp"
android:layout_height=
"wrap_content"
app:layout_columnWeight=
"1"
app:layout_gravity=
"fill_horizontal"
/>
<TextView
app:layout_row=
"1"
app:layout_column=
"2"
android:textSize=
"14sp"
android:textColor=
"?android:attr/textColorSecondary"
android:ellipsize=
"marquee"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:text=
"▼"
/>
<TextView
app:layout_row=
"1"
app:layout_column=
"3"
android:id=
"@+id/rxRate"
android:textSize=
"14sp"
android:textColor=
"?android:attr/textColorSecondary"
android:ellipsize=
"marquee"
android:layout_width=
"0dp"
android:layout_height=
"wrap_content"
app:layout_columnWeight=
"1"
app:layout_gravity=
"fill_horizontal"
android:gravity=
"end"
/>
</android.support.v7.widget.GridLayout>
<FrameLayout
android:id=
"@+id/content"
android:layout_width=
"match_parent"
android:layout_height=
"match_parent"
android:layout_above=
"@+id/stat"
/>
<LinearLayout
android:id=
"@+id/stat"
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
android:elevation=
"4dp"
android:paddingStart=
"16dip"
android:paddingEnd=
"16dip"
android:paddingTop=
"16dip"
android:paddingBottom=
"16dip"
android:focusable=
"true"
android:background=
"@drawable/background_stat"
android:orientation=
"vertical"
android:nextFocusRight=
"@+id/fab"
android:layout_alignParentBottom=
"true"
>
<LinearLayout
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
>
<TextView
android:text=
"@string/stat"
android:textSize=
"16sp"
android:textColor=
"?android:attr/textColorPrimary"
android:ellipsize=
"marquee"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
/>
<TextView
android:id=
"@+id/connection_test"
android:gravity=
"end"
android:textSize=
"14sp"
android:textColor=
"?android:attr/textColorSecondary"
android:layout_width=
"0dp"
android:layout_height=
"wrap_content"
android:layout_weight=
"1"
/>
</LinearLayout>
<android.support.v7.widget.GridLayout
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
android:paddingEnd=
"72dip"
app:columnCount=
"4"
>
<TextView
app:layout_column=
"0"
style=
"@style/TextAppearance.AppCompat.Body2"
android:ellipsize=
"marquee"
android:text=
"@string/sent"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:layout_marginEnd=
"24dp"
android:gravity=
"end"
/>
<TextView
app:layout_column=
"1"
android:id=
"@+id/tx"
android:textSize=
"14sp"
android:textColor=
"?android:attr/textColorSecondary"
android:ellipsize=
"marquee"
android:layout_width=
"0dp"
android:layout_height=
"wrap_content"
app:layout_columnWeight=
"1"
app:layout_gravity=
"fill_horizontal"
/>
<TextView
app:layout_column=
"2"
android:textSize=
"14sp"
android:textColor=
"?android:attr/textColorSecondary"
android:ellipsize=
"marquee"
android:text=
"▲"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
/>
<TextView
app:layout_column=
"3"
android:id=
"@+id/txRate"
android:textSize=
"14sp"
android:textColor=
"?android:attr/textColorSecondary"
android:ellipsize=
"marquee"
android:layout_width=
"0dp"
android:layout_height=
"wrap_content"
app:layout_columnWeight=
"1"
app:layout_gravity=
"fill_horizontal"
android:gravity=
"end"
/>
<TextView
app:layout_row=
"1"
app:layout_column=
"0"
style=
"@style/TextAppearance.AppCompat.Body2"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:layout_marginEnd=
"24dp"
android:gravity=
"end"
android:ellipsize=
"marquee"
android:text=
"@string/received"
/>
<TextView
app:layout_row=
"1"
app:layout_column=
"1"
android:id=
"@+id/rx"
android:textSize=
"14sp"
android:textColor=
"?android:attr/textColorSecondary"
android:ellipsize=
"marquee"
android:layout_width=
"0dp"
android:layout_height=
"wrap_content"
app:layout_columnWeight=
"1"
app:layout_gravity=
"fill_horizontal"
/>
<TextView
app:layout_row=
"1"
app:layout_column=
"2"
android:textSize=
"14sp"
android:textColor=
"?android:attr/textColorSecondary"
android:ellipsize=
"marquee"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:text=
"▼"
/>
<TextView
app:layout_row=
"1"
app:layout_column=
"3"
android:id=
"@+id/rxRate"
android:textSize=
"14sp"
android:textColor=
"?android:attr/textColorSecondary"
android:ellipsize=
"marquee"
android:layout_width=
"0dp"
android:layout_height=
"wrap_content"
app:layout_columnWeight=
"1"
app:layout_gravity=
"fill_horizontal"
android:gravity=
"end"
/>
</android.support.v7.widget.GridLayout>
</LinearLayout>
<fragment
android:name=
"com.github.shadowsocks.ShadowsocksSettings"
android:id=
"@android:id/content"
android:layout_below=
"@+id/title_bar"
android:layout_width=
"match_parent"
android:layout_height=
"match_parent"
/>
<com.github.jorgecastilloprz.FABProgressCircle
android:id=
"@+id/fabProgressCircle"
app:reusable=
"true"
<com.github.jorgecastilloprz.FABProgressCircle
android:id=
"@+id/fabProgressCircle"
app:reusable=
"true"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:elevation=
"6dp"
android:layout_alignBottom=
"@+id/title_bar"
android:layout_height=
"wrap_content"
android:elevation=
"6dp"
android:layout_alignTop=
"@+id/stat"
android:layout_alignParentEnd=
"true"
android:layout_marginEnd=
"16dp"
android:layout_margin
Bottom=
"@dimen/fab_margin_bottom
"
>
android:layout_margin
Top=
"@dimen/fab_margin_top
"
>
<android.support.design.widget.FloatingActionButton
android:id=
"@+id/fab"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
app:backgroundTint=
"@color/material_blue_grey_700"
...
...
src/main/res/layout/layout_profile_config.xml
0 → 100644
View file @
dbb137c5
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android=
"http://schemas.android.com/apk/res/android"
android:orientation=
"vertical"
android:layout_width=
"match_parent"
android:layout_height=
"match_parent"
>
<include
layout=
"@layout/toolbar_light_dark"
/>
<fragment
android:id=
"@+id/content"
class=
"com.github.shadowsocks.ProfileConfigFragment"
android:layout_width=
"match_parent"
android:layout_height=
"0dp"
android:layout_weight=
"1"
/>
</LinearLayout>
src/main/res/layout/layout_profiles.xml
View file @
dbb137c5
<?xml version="1.0" encoding="utf-8"?>
<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:orientation=
"vertical"
android:layout_height=
"match_parent"
>
<include
layout=
"@layout/toolbar_light_dark"
/>
<android.support.design.widget.CoordinatorLayout
android:layout_width=
"match_parent"
android:layout_height=
"0dp"
android:layout_weight=
"1"
>
<android.support.v7.widget.RecyclerView
android:id=
"@+id/profilesList"
android:layout_width=
"match_parent"
android:layout_height=
"match_parent"
/>
<com.github.clans.fab.FloatingActionMenu
android:id=
"@+id/menu"
android:layout_width=
"match_parent"
android:layout_height=
"match_parent"
android:layout_gravity=
"bottom|end"
android:layout_marginBottom=
"16dp"
android:layout_marginEnd=
"16dp"
app:menu_colorNormal=
"@color/material_primary_500"
app:menu_colorPressed=
"@color/material_primary_700"
app:fab_colorRipple=
"@android:color/white"
app:layout_behavior=
"com.github.shadowsocks.widget.FloatingActionMenuBehavior"
>
<com.github.clans.fab.FloatingActionButton
android:id=
"@+id/fab_manual_add"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
app:fab_colorNormal=
"@color/material_primary_500"
app:fab_colorPressed=
"@color/material_primary_700"
app:fab_colorRipple=
"@android:color/white"
app:fab_size=
"mini"
app:fab_label=
"@string/add_profile_methods_manual_settings"
/>
<com.github.clans.fab.FloatingActionButton
android:id=
"@+id/fab_qrcode_add"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
app:fab_colorNormal=
"@color/material_primary_500"
app:fab_colorPressed=
"@color/material_primary_700"
app:fab_colorRipple=
"@android:color/white"
app:fab_size=
"mini"
app:fab_label=
"@string/add_profile_methods_scan_qr_code"
/>
<com.github.clans.fab.FloatingActionButton
android:id=
"@+id/fab_nfc_add"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
app:fab_colorNormal=
"@color/material_primary_500"
app:fab_colorPressed=
"@color/material_primary_700"
app:fab_colorRipple=
"@android:color/white"
app:fab_size=
"mini"
app:fab_label=
"NFC"
/>
<com.github.clans.fab.FloatingActionButton
android:id=
"@+id/fab_import_add"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
app:fab_colorNormal=
"@color/material_primary_500"
app:fab_colorPressed=
"@color/material_primary_700"
app:fab_colorRipple=
"@android:color/white"
app:fab_size=
"mini"
app:fab_label=
"@string/action_import"
/>
</com.github.clans.fab.FloatingActionMenu>
</android.support.design.widget.CoordinatorLayout>
android:layout_height=
"match_parent"
android:orientation=
"vertical"
>
<include
layout=
"@layout/toolbar_light_dark"
/>
<android.support.v7.widget.RecyclerView
xmlns:android=
"http://schemas.android.com/apk/res/android"
android:id=
"@+id/profilesList"
android:layout_width=
"match_parent"
android:layout_height=
"0dp"
android:layout_weight=
"1"
android:paddingBottom=
"8dp"
/>
</LinearLayout>
src/main/res/layout/layout_profiles_item.xml
View file @
dbb137c5
<?xml version="1.0" encoding="utf-8"?>
<!-- Based on: https://github.com/android/platform_frameworks_base/blob/master/core/res/res/layout/select_dialog_singlechoice_material.xml -->
<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=
"wrap_content"
android:id=
"@+id/container"
android:background=
"?android:attr/selectableItemBackground"
android:paddingStart=
"20dp"
android:paddingEnd=
"?attr/dialogPreferredPadding"
android:focusable=
"true"
android:nextFocusRight=
"@+id/share"
>
<CheckedTextView
xmlns:android=
"http://schemas.android.com/apk/res/android"
android:id=
"@android:id/text1"
android:layout_width=
"0dp"
android:layout_height=
"wrap_content"
android:layout_gravity=
"center_vertical"
android:layout_weight=
"1"
android:minHeight=
"?android:attr/listPreferredItemHeightSmall"
android:textAppearance=
"?android:attr/textAppearanceMedium"
android:textColor=
"?android:attr/textColorAlertDialogListItem"
android:gravity=
"center_vertical"
android:drawableStart=
"?android:attr/listChoiceIndicatorSingle"
android:drawablePadding=
"20dp"
android:maxLines=
"4"
android:scrollHorizontally=
"false"
/>
<ImageView
android:id=
"@+id/share"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:padding=
"5dp"
android:layout_gravity=
"center_vertical"
app:srcCompat=
"@drawable/ic_social_share"
android:background=
"?attr/selectableItemBackgroundBorderless"
android:contentDescription=
"@string/share"
android:focusable=
"true"
android:nextFocusLeft=
"@+id/container"
/>
</LinearLayout>
<!-- Based on: https://github.com/android/platform_frameworks_base/blob/505e3ab/core/res/res/layout/simple_list_item_2.xml -->
<android.support.v7.widget.CardView
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=
"wrap_content"
android:background=
"?android:attr/selectableItemBackground"
android:focusable=
"true"
android:nextFocusRight=
"@+id/edit"
android:layout_marginTop=
"8dp"
android:layout_marginStart=
"8dp"
android:layout_marginEnd=
"8dp"
>
<LinearLayout
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
android:orientation=
"vertical"
android:layout_margin=
"8dp"
>
<LinearLayout
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
android:id=
"@+id/container"
>
<TextView
android:id=
"@android:id/text1"
android:layout_width=
"0dp"
android:layout_height=
"wrap_content"
android:layout_gravity=
"center_vertical"
android:layout_weight=
"1"
android:textAppearance=
"?android:attr/textAppearanceLarge"
android:textColor=
"?android:attr/textColorPrimary"
android:gravity=
"center_vertical"
android:maxLines=
"2"
android:ellipsize=
"end"
/>
<ImageView
android:id=
"@+id/edit"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:padding=
"4dp"
android:layout_gravity=
"top"
app:srcCompat=
"@drawable/ic_image_edit"
android:background=
"?attr/selectableItemBackgroundBorderless"
android:contentDescription=
"Edit"
android:focusable=
"true"
android:nextFocusLeft=
"@+id/container"
/>
</LinearLayout>
<TextView
android:id=
"@android:id/text2"
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
android:textAppearance=
"?android:attr/textAppearanceMedium"
android:textColor=
"?android:attr/textColorSecondary"
/>
</LinearLayout>
</android.support.v7.widget.CardView>
src/main/res/menu/profile_config_menu.xml
0 → 100644
View file @
dbb137c5
<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android=
"http://schemas.android.com/apk/res/android"
xmlns:app=
"http://schemas.android.com/apk/res-auto"
>
<item
android:title=
"@string/share"
android:icon=
"@drawable/abc_ic_menu_share_mtrl_alpha"
android:alphabeticShortcut=
"s"
android:numericShortcut=
"1"
app:showAsAction=
"always"
>
<menu>
<item
android:id=
"@+id/action_qr_code_nfc"
android:title=
"QR code/NFC"
android:alphabeticShortcut=
"q"
android:numericShortcut=
"1"
/>
<item
android:id=
"@+id/action_export"
android:title=
"@string/action_export"
android:alphabeticShortcut=
"e"
android:numericShortcut=
"2"
/>
</menu>
</item>
<item
android:id=
"@+id/action_delete"
android:title=
"Delete"
android:icon=
"@drawable/ic_action_delete"
android:alphabeticShortcut=
"d"
android:numericShortcut=
"2"
app:showAsAction=
"always"
/>
<item
android:id=
"@+id/action_apply"
android:title=
"Apply"
android:icon=
"@drawable/ic_action_done"
android:alphabeticShortcut=
"a"
android:numericShortcut=
"3"
app:showAsAction=
"always"
/>
</menu>
src/main/res/menu/profile_manager_menu.xml
View file @
dbb137c5
<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android=
"http://schemas.android.com/apk/res/android"
xmlns:app=
"http://schemas.android.com/apk/res-auto"
>
<item
android:title=
"@string/add_profile"
android:icon=
"@drawable/ic_content_add"
android:alphabeticShortcut=
"a"
android:numericShortcut=
"0"
app:showAsAction=
"always"
>
<menu>
<item
android:id=
"@+id/action_scan_qr_code"
android:title=
"@string/add_profile_methods_scan_qr_code"
android:alphabeticShortcut=
"s"
android:numericShortcut=
"1"
/>
<item
android:id=
"@+id/action_import"
android:title=
"@string/action_import"
android:alphabeticShortcut=
"i"
android:numericShortcut=
"2"
/>
<item
android:id=
"@+id/action_manual_settings"
android:title=
"@string/add_profile_methods_manual_settings"
android:alphabeticShortcut=
"m"
android:numericShortcut=
"3"
/>
</menu>
</item>
<item
android:id=
"@+id/action_export"
android:title=
"@string/action_export"
android:alphabeticShortcut=
"e"
...
...
src/main/res/values-v21/dimen.xml
View file @
dbb137c5
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen
name=
"fab_margin_
bottom
"
>
-28dp
</dimen>
<dimen
name=
"fab_margin_
top
"
>
-28dp
</dimen>
</resources>
src/main/res/values/colors.xml
View file @
dbb137c5
...
...
@@ -3,12 +3,13 @@
<resources>
<color
name=
"material_green_700"
>
#388E3C
</color>
<color
name=
"material_green_a700"
>
#00C853
</color>
<color
name=
"material_blue_grey_300"
>
#90A4AE
</color>
<color
name=
"material_blue_grey_500"
>
#607D8B
</color>
<color
name=
"material_blue_grey_700"
>
#455A64
</color>
<color
name=
"material_primary_300"
>
@color/material_blue_grey_300
</color>
<color
name=
"material_primary_500"
>
@color/material_blue_grey_500
</color>
<color
name=
"material_primary_700"
>
@color/material_blue_grey_700
</color>
<color
name=
"material_accent_200"
>
@color/material_
blue_grey_5
00
</color>
<color
name=
"material_accent_200"
>
@color/material_
green_a7
00
</color>
</resources>
src/main/res/values/dimen.xml
View file @
dbb137c5
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- TODO: Remove when this issue is fixed: https://github.com/JorgeCastilloPrz/FABProgressCircle/issues/19 -->
<dimen
name=
"fab_margin_
bottom
"
>
-44dp
</dimen>
<dimen
name=
"fab_margin_
top
"
>
-44dp
</dimen>
</resources>
src/main/res/values/styles.xml
View file @
dbb137c5
...
...
@@ -6,11 +6,11 @@
<item
name=
"android:textSize"
>
24sp
</item>
</style>
<style
name=
"Theme.MaterialBase"
parent=
"
Theme.AppCompat.Light.NoActionB
ar"
>
<style
name=
"Theme.MaterialBase"
parent=
"
MaterialDrawerTheme.Light.DarkToolb
ar"
>
<!--suppress NewApi -->
<item
name=
"android:navigationBarColor"
>
@color/material_primary_700
</item>
<item
name=
"colorAccent"
>
@color/material_primary_500
</item>
<item
name=
"colorButtonNormal"
>
@color/material_
primary_5
00
</item>
<item
name=
"colorButtonNormal"
>
@color/material_
accent_2
00
</item>
<item
name=
"colorPrimary"
>
@color/material_primary_500
</item>
<item
name=
"colorPrimaryDark"
>
@color/material_primary_700
</item>
<item
name=
"preferenceTheme"
>
@style/Theme.Material.PreferenceThemeOverlay
</item>
...
...
src/main/res/xml/pref_global.xml
0 → 100644
View file @
dbb137c5
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android=
"http://schemas.android.com/apk/res/android"
>
<SwitchPreference
android:key=
"isAutoConnect"
android:persistent=
"false"
android:summary=
"@string/auto_connect_summary"
android:title=
"@string/auto_connect"
/>
<SwitchPreference
android:key=
"tcp_fastopen"
android:summary=
"@string/tcp_fastopen_summary"
android:title=
"TCP Fast Open"
/>
<SwitchPreference
android:key=
"isNAT"
android:title=
"@string/nat"
android:summary=
"@string/nat_summary"
/>
<Preference
android:key=
"recovery"
android:title=
"@string/recovery"
android:summary=
"@string/recovery_summary"
/>
</PreferenceScreen>
src/main/res/xml/pref_
all
.xml
→
src/main/res/xml/pref_
profile
.xml
View file @
dbb137c5
...
...
@@ -7,44 +7,37 @@
<be.mygod.preference.EditTextPreference
android:key=
"profileName"
android:persistent=
"false"
android:summary=
"%s"
android:title=
"@string/profile_name"
/>
<be.mygod.preference.EditTextPreference
android:key=
"proxy"
android:persistent=
"false"
android:summary=
"%s"
android:title=
"@string/proxy"
/>
<be.mygod.preference.NumberPickerPreference
app:min=
"1"
app:max=
"65535"
android:key=
"remotePortNum"
android:persistent=
"false"
android:summary=
"%d"
android:title=
"@string/remote_port"
/>
<be.mygod.preference.NumberPickerPreference
app:min=
"1025"
app:max=
"65535"
android:key=
"localPortNum"
android:persistent=
"false"
android:summary=
"%d"
android:title=
"@string/port"
/>
<be.mygod.preference.EditTextPreference
android:inputType=
"textPassword"
android:key=
"sitekey"
android:persistent=
"false"
android:summary=
"%s"
android:title=
"@string/sitekey"
/>
<DropDownPreference
android:key=
"encMethod"
android:persistent=
"false"
android:entries=
"@array/enc_method_entry"
android:entryValues=
"@array/enc_method_value"
android:summary=
"%s"
android:title=
"@string/enc_method"
/>
<SwitchPreference
android:key=
"isAuth"
android:persistent=
"false"
android:summary=
"@string/onetime_auth_summary"
android:title=
"@string/onetime_auth"
/>
...
...
@@ -55,7 +48,6 @@
<DropDownPreference
android:key=
"route"
android:persistent=
"false"
android:entries=
"@array/route_entry"
android:entryValues=
"@array/route_value"
android:summary=
"%s"
...
...
@@ -63,20 +55,17 @@
<be.mygod.preference.EditTextPreference
android:key=
"remoteDns"
android:title=
"@string/remote_dns"
android:summary=
"
@string/remote_dns_summary
"
/>
android:summary=
"
%s
"
/>
<SwitchPreference
android:key=
"isIpv6"
android:persistent=
"false"
android:summary=
"@string/ipv6_summary"
android:title=
"@string/ipv6"
/>
<SwitchPreference
android:key=
"isProxyApps"
android:persistent=
"false"
android:summary=
"@string/proxied_apps_summary"
android:title=
"@string/proxied_apps"
/>
<SwitchPreference
android:key=
"isUdpDns"
android:persistent=
"false"
android:summary=
"@string/udp_dns_summary"
android:title=
"@string/udp_dns"
/>
...
...
@@ -87,7 +76,6 @@
<SwitchPreference
android:key=
"kcp"
android:persistent=
"false"
android:summary=
"@string/kcp_summary"
android:title=
"@string/kcp"
/>
...
...
@@ -95,41 +83,14 @@
app:min=
"1"
app:max=
"65535"
android:key=
"kcpPort"
android:persistent=
"false"
android:summary=
"@string/kcp_port_summary"
android:title=
"@string/kcp_port"
/>
<be.mygod.preference.EditTextPreference
android:key=
"kcpcli"
android:persistent=
"false"
android:summary=
"%s"
android:title=
"@string/kcpcli"
/>
</be.mygod.preference.PreferenceCategory>
<be.mygod.preference.PreferenceCategory
android:title=
"@string/misc_cat"
>
<SwitchPreference
android:key=
"isAutoConnect"
android:persistent=
"false"
android:summary=
"@string/auto_connect_summary"
android:title=
"@string/auto_connect"
/>
<SwitchPreference
android:key=
"tcp_fastopen"
android:summary=
"@string/tcp_fastopen_summary"
android:title=
"TCP Fast Open"
/>
<SwitchPreference
android:key=
"isNAT"
android:title=
"@string/nat"
android:summary=
"@string/nat_summary"
/>
<Preference
android:key=
"recovery"
android:title=
"@string/recovery"
android:summary=
"@string/recovery_summary"
/>
<Preference
android:key=
"about"
android:title=
"@string/about"
/>
</be.mygod.preference.PreferenceCategory>
</PreferenceScreen>
src/main/scala/com/github/shadowsocks/AppManager.scala
View file @
dbb137c5
...
...
@@ -98,10 +98,7 @@ class AppManager extends AppCompatActivity with OnMenuItemClickListener {
proxiedApps
.
add
(
item
.
packageName
)
check
.
setChecked
(
true
)
}
if
(!
appsLoading
.
get
)
{
profile
.
individual
=
proxiedApps
.
mkString
(
"\n"
)
app
.
profileManager
.
updateProfile
(
profile
)
}
if
(!
appsLoading
.
get
)
app
.
editor
.
putString
(
Key
.
individual
,
proxiedApps
.
mkString
(
"\n"
)).
apply
()
}
}
...
...
@@ -124,9 +121,9 @@ class AppManager extends AppCompatActivity with OnMenuItemClickListener {
private
var
loadingView
:
View
=
_
private
val
appsLoading
=
new
AtomicBoolean
private
var
handler
:
Handler
=
_
private
val
profile
=
app
.
currentProfile
.
orNull
private
def
initProxiedApps
(
str
:
String
=
profile
.
individual
)
=
proxiedApps
=
str
.
split
(
'\n'
).
to
[
mutable.HashSet
]
private
def
initProxiedApps
(
str
:
String
=
app
.
settings
.
getString
(
Key
.
individual
,
null
))
=
proxiedApps
=
str
.
split
(
'\n'
).
to
[
mutable.HashSet
]
override
def
onDestroy
()
{
instance
=
null
...
...
@@ -143,7 +140,7 @@ class AppManager extends AppCompatActivity with OnMenuItemClickListener {
case
R
.
id
.
action_apply_all
=>
app
.
profileManager
.
getAllProfiles
match
{
case
Some
(
profiles
)
=>
val
proxiedAppString
=
profile
.
individual
val
proxiedAppString
=
app
.
settings
.
getString
(
Key
.
individual
,
null
)
profiles
.
foreach
(
p
=>
{
p
.
individual
=
proxiedAppString
app
.
profileManager
.
updateProfile
(
p
)
...
...
@@ -153,8 +150,8 @@ class AppManager extends AppCompatActivity with OnMenuItemClickListener {
}
return
true
case
R
.
id
.
action_export
=>
val
bypass
=
profile
.
bypass
val
proxiedAppString
=
profile
.
individual
val
bypass
=
app
.
settings
.
getBoolean
(
Key
.
bypass
,
false
)
val
proxiedAppString
=
app
.
settings
.
getString
(
Key
.
individual
,
null
)
val
clip
=
ClipData
.
newPlainText
(
Key
.
individual
,
bypass
+
"\n"
+
proxiedAppString
)
clipboard
.
setPrimaryClip
(
clip
)
Toast
.
makeText
(
this
,
R
.
string
.
action_export_msg
,
Toast
.
LENGTH_SHORT
).
show
()
...
...
@@ -170,8 +167,7 @@ class AppManager extends AppCompatActivity with OnMenuItemClickListener {
val
(
enabled
,
apps
)
=
if
(
i
<
0
)
(
proxiedAppString
,
""
)
else
(
proxiedAppString
.
substring
(
0
,
i
),
proxiedAppString
.
substring
(
i
+
1
))
bypassSwitch
.
setChecked
(
enabled
.
toBoolean
)
profile
.
individual
=
apps
app
.
profileManager
.
updateProfile
(
profile
)
app
.
editor
.
putString
(
Key
.
individual
,
apps
).
apply
()
Toast
.
makeText
(
this
,
R
.
string
.
action_import_msg
,
Toast
.
LENGTH_SHORT
).
show
()
appListView
.
setVisibility
(
View
.
GONE
)
loadingView
.
setVisibility
(
View
.
VISIBLE
)
...
...
@@ -194,8 +190,6 @@ class AppManager extends AppCompatActivity with OnMenuItemClickListener {
protected
override
def
onCreate
(
savedInstanceState
:
Bundle
)
{
super
.
onCreate
(
savedInstanceState
)
if
(
profile
==
null
)
finish
()
handler
=
new
Handler
()
this
.
setContentView
(
R
.
layout
.
layout_apps
)
...
...
@@ -211,23 +205,16 @@ class AppManager extends AppCompatActivity with OnMenuItemClickListener {
toolbar
.
inflateMenu
(
R
.
menu
.
app_manager_menu
)
toolbar
.
setOnMenuItemClickListener
(
this
)
if
(!
profile
.
proxyApps
)
{
profile
.
proxyApps
=
true
app
.
profileManager
.
updateProfile
(
profile
)
}
if
(!
app
.
settings
.
getBoolean
(
Key
.
proxyApps
,
false
))
app
.
editor
.
putBoolean
(
Key
.
proxyApps
,
true
).
apply
()
findViewById
(
R
.
id
.
onSwitch
).
asInstanceOf
[
Switch
]
.
setOnCheckedChangeListener
((
_
,
checked
)
=>
{
profile
.
proxyApps
=
checked
app
.
profileManager
.
updateProfile
(
profile
)
app
.
editor
.
putBoolean
(
Key
.
proxyApps
,
checked
).
apply
()
finish
()
})
bypassSwitch
=
findViewById
(
R
.
id
.
bypassSwitch
).
asInstanceOf
[
Switch
]
bypassSwitch
.
setChecked
(
profile
.
bypass
)
bypassSwitch
.
setOnCheckedChangeListener
((
_
,
checked
)
=>
{
profile
.
bypass
=
checked
app
.
profileManager
.
updateProfile
(
profile
)
})
bypassSwitch
.
setChecked
(
app
.
settings
.
getBoolean
(
Key
.
bypass
,
false
))
bypassSwitch
.
setOnCheckedChangeListener
((
_
,
checked
)
=>
app
.
editor
.
putBoolean
(
Key
.
bypass
,
checked
).
apply
())
initProxiedApps
()
loadingView
=
findViewById
(
R
.
id
.
loading
)
...
...
src/main/scala/com/github/shadowsocks/GlobalConfigFragment.scala
0 → 100644
View file @
dbb137c5
package
com.github.shadowsocks
import
android.app.ProgressDialog
import
android.os.
{
Bundle
,
Handler
,
Message
}
import
android.support.design.widget.Snackbar
import
android.support.v14.preference.SwitchPreference
import
be.mygod.preference.PreferenceFragment
import
com.github.shadowsocks.ShadowsocksApplication.app
import
com.github.shadowsocks.utils.
{
Key
,
TcpFastOpen
,
Utils
}
class
GlobalConfigFragment
extends
PreferenceFragment
{
private
var
progressDialog
:
ProgressDialog
=
_
override
def
onCreatePreferences
(
bundle
:
Bundle
,
key
:
String
)
{
addPreferencesFromResource
(
R
.
xml
.
pref_global
)
val
switch
=
findPreference
(
Key
.
isAutoConnect
).
asInstanceOf
[
SwitchPreference
]
switch
.
setOnPreferenceChangeListener
((
_
,
value
)
=>
{
BootReceiver
.
setEnabled
(
getActivity
,
value
.
asInstanceOf
[
Boolean
])
true
})
if
(
getPreferenceManager
.
getSharedPreferences
.
getBoolean
(
Key
.
isAutoConnect
,
false
))
{
BootReceiver
.
setEnabled
(
getActivity
,
true
)
getPreferenceManager
.
getSharedPreferences
.
edit
.
remove
(
Key
.
isAutoConnect
).
apply
()
}
switch
.
setChecked
(
BootReceiver
.
getEnabled
(
getActivity
))
val
tfo
=
findPreference
(
Key
.
tfo
).
asInstanceOf
[
SwitchPreference
]
tfo
.
setChecked
(
TcpFastOpen
.
sendEnabled
)
tfo
.
setOnPreferenceChangeListener
((
_
,
v
)
=>
{
val
value
=
v
.
asInstanceOf
[
Boolean
]
val
result
=
TcpFastOpen
.
enabled
(
value
)
if
(
result
!=
null
&&
result
!=
"Success."
)
Snackbar
.
make
(
getActivity
.
findViewById
(
android
.
R
.
id
.
content
),
result
,
Snackbar
.
LENGTH_LONG
).
show
()
value
==
TcpFastOpen
.
sendEnabled
})
if
(!
TcpFastOpen
.
supported
)
{
tfo
.
setEnabled
(
false
)
tfo
.
setSummary
(
getString
(
R
.
string
.
tcp_fastopen_summary_unsupported
,
java
.
lang
.
System
.
getProperty
(
"os.version"
)))
}
findPreference
(
"recovery"
).
setOnPreferenceClickListener
(
_
=>
{
app
.
track
(
"GlobalConfigFragment"
,
"reset"
)
Utils
.
stopSsService
(
getActivity
)
val
h
=
showProgress
(
R
.
string
.
recovering
)
Utils
.
ThrowableFuture
{
app
.
copyAssets
()
h
.
sendEmptyMessage
(
0
)
}
true
})
}
private
def
showProgress
(
msg
:
Int
)
:
Handler
=
{
clearDialog
()
progressDialog
=
ProgressDialog
.
show
(
getActivity
,
""
,
getString
(
msg
),
true
,
false
)
new
Handler
{
override
def
handleMessage
(
msg
:
Message
)
{
clearDialog
()
}
}
}
def
clearDialog
()
{
if
(
progressDialog
!=
null
&&
progressDialog
.
isShowing
)
{
if
(!
getActivity
.
isDestroyed
)
progressDialog
.
dismiss
()
progressDialog
=
null
}
}
}
src/main/scala/com/github/shadowsocks/GlobalSettingsFragment.scala
0 → 100644
View file @
dbb137c5
package
com.github.shadowsocks
import
android.os.Bundle
import
android.view.
{
LayoutInflater
,
View
,
ViewGroup
}
class
GlobalSettingsFragment
extends
ToolbarFragment
{
override
def
onCreateView
(
inflater
:
LayoutInflater
,
container
:
ViewGroup
,
savedInstanceState
:
Bundle
)
:
View
=
inflater
.
inflate
(
R
.
layout
.
layout_global_settings
,
container
,
false
)
override
def
onViewCreated
(
view
:
View
,
savedInstanceState
:
Bundle
)
{
super
.
onViewCreated
(
view
,
savedInstanceState
)
toolbar
.
setTitle
(
R
.
string
.
settings
)
}
}
src/main/scala/com/github/shadowsocks/
Shadowsocks
.scala
→
src/main/scala/com/github/shadowsocks/
MainActivity
.scala
View file @
dbb137c5
...
...
@@ -22,69 +22,58 @@ package com.github.shadowsocks
import
java.lang.System.currentTimeMillis
import
java.net.
{
HttpURLConnection
,
URL
}
import
java.util
import
java.util.
{
GregorianCalendar
,
Locale
}
import
java.util.Locale
import
android.app.Activity
import
android.app.backup.BackupManager
import
android.
app.
{
Activity
,
ProgressDialog
}
import
android.
content.SharedPreferences.OnSharedPreferenceChangeListener
import
android.content._
import
android.
graphics.Typeface
import
android.n
et.VpnService
import
android.os.
_
import
android.
net.
{
Uri
,
VpnService
}
import
android.n
fc.
{
NdefMessage
,
NfcAdapter
}
import
android.os.
{
Build
,
Bundle
,
Handler
}
import
android.support.design.widget.
{
FloatingActionButton
,
Snackbar
}
import
android.support.v4.content.ContextCompat
import
android.support.v7.app.AppCompatActivity
import
android.support.v7.widget.Toolbar
import
android.support.v7.app.AlertDialog
import
android.support.v7.widget.RecyclerView.ViewHolder
import
android.text.TextUtils
import
android.util.Log
import
android.view.
{
View
,
ViewGroup
}
import
android.widget._
import
android.view.View
import
android.webkit.
{
WebView
,
WebViewClient
}
import
android.widget.
{
TextView
,
Toast
}
import
com.github.jorgecastilloprz.FABProgressCircle
import
com.github.shadowsocks.ShadowsocksApplication.app
import
com.github.shadowsocks.aidl.IShadowsocksServiceCallback
import
com.github.shadowsocks.database.
_
import
com.github.shadowsocks.utils.CloseUtils.
_
import
com.github.shadowsocks.database.
Profile
import
com.github.shadowsocks.utils.CloseUtils.
autoDisconnect
import
com.github.shadowsocks.utils._
import
com.google.android.gms.ads.
{
AdRequest
,
AdSize
,
AdView
}
import
scala.util.Random
object
Typefaces
{
def
get
(
c
:
Context
,
assetPath
:
String
)
:
Typeface
=
{
cache
synchronized
{
if
(!
cache
.
containsKey
(
assetPath
))
{
try
{
cache
.
put
(
assetPath
,
Typeface
.
createFromAsset
(
c
.
getAssets
,
assetPath
))
}
catch
{
case
e
:
Exception
=>
Log
.
e
(
TAG
,
"Could not get typeface '"
+
assetPath
+
"' because "
+
e
.
getMessage
)
app
.
track
(
e
)
return
null
}
}
return
cache
.
get
(
assetPath
)
}
}
import
com.google.android.gms.ads.AdView
import
com.mikepenz.materialdrawer.model.interfaces.IDrawerItem
import
com.mikepenz.materialdrawer.model.
{
PrimaryDrawerItem
,
SecondaryDrawerItem
}
import
com.mikepenz.materialdrawer.
{
Drawer
,
DrawerBuilder
}
private
final
val
TAG
=
"Typefaces"
private
final
val
cache
=
new
util
.
Hashtable
[
String
,
Typeface
]
}
object
Shadowsocks
{
// Constants
private
final
val
TAG
=
"Shadowsocks"
object
MainActivity
{
private
final
val
TAG
=
"MainActivity"
private
final
val
REQUEST_CONNECT
=
1
private
final
val
DRAWER_PROFILES
=
0L
// sticky drawer items have negative ids
private
final
val
DRAWER_GLOBAL_SETTINGS
=
-
1L
private
final
val
DRAWER_ABOUT
=
-
2L
}
class
Shadowsocks
extends
AppCompatActivity
with
ServiceBoundContext
{
import
Shadowsocks._
class
MainActivity
extends
Activity
with
ServiceBoundContext
with
Drawer
.
OnDrawerItemClickListener
with
OnSharedPreferenceChangeListener
{
import
MainActivity._
// Variables
var
serviceStarted
:
Boolean
=
_
var
fab
:
FloatingActionButton
=
_
var
fabProgressCircle
:
FABProgressCircle
=
_
var
progressDialog
:
ProgressDialog
=
_
var
state
=
State
.
STOPPED
var
currentProfile
=
new
Profile
var
drawer
:
Drawer
=
_
private
lazy
val
profilesFragment
=
new
ProfilesFragment
()
private
lazy
val
globalSettingsFragment
=
new
GlobalSettingsFragment
()
// Services
private
val
callback
=
new
IShadowsocksServiceCallback
.
Stub
{
...
...
@@ -96,8 +85,7 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext {
fab
.
setImageResource
(
R
.
drawable
.
ic_start_busy
)
fab
.
setEnabled
(
false
)
fabProgressCircle
.
show
()
preferences
.
setEnabled
(
false
)
stat
.
setVisibility
(
View
.
GONE
)
//stat.setVisibility(View.GONE)
case
State
.
CONNECTED
=>
fab
.
setBackgroundTintList
(
greenTint
)
if
(
state
==
State
.
CONNECTING
)
{
...
...
@@ -107,7 +95,6 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext {
}
fab
.
setEnabled
(
true
)
changeSwitch
(
checked
=
true
)
preferences
.
setEnabled
(
false
)
stat
.
setVisibility
(
View
.
VISIBLE
)
if
(
app
.
isNatEnabled
)
connectionTestText
.
setVisibility
(
View
.
GONE
)
else
{
connectionTestText
.
setVisibility
(
View
.
VISIBLE
)
...
...
@@ -121,20 +108,17 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext {
if
(
m
!=
null
)
{
val
snackbar
=
Snackbar
.
make
(
findViewById
(
android
.
R
.
id
.
content
),
getString
(
R
.
string
.
vpn_error
).
formatLocal
(
Locale
.
ENGLISH
,
m
),
Snackbar
.
LENGTH_LONG
)
if
(
m
==
getString
(
R
.
string
.
nat_no_root
))
snackbar
.
setAction
(
R
.
string
.
switch_to_vpn
,
(
_
=>
preferences
.
natSwitch
.
setChecked
(
false
))
:
View.OnClickListener
)
if
(
m
==
getString
(
R
.
string
.
nat_no_root
))
addDisableNatToSnackbar
(
snackbar
)
snackbar
.
show
()
Log
.
e
(
TAG
,
"Error to start VPN service: "
+
m
)
}
preferences
.
setEnabled
(
true
)
stat
.
setVisibility
(
View
.
GONE
)
//stat.setVisibility(View.GONE)
case
State
.
STOPPING
=>
fab
.
setBackgroundTintList
(
greyTint
)
fab
.
setImageResource
(
R
.
drawable
.
ic_start_busy
)
fab
.
setEnabled
(
false
)
if
(
state
==
State
.
CONNECTED
)
fabProgressCircle
.
show
()
// ignore for stopped
preferences
.
setEnabled
(
false
)
stat
.
setVisibility
(
View
.
GONE
)
//stat.setVisibility(View.GONE)
}
state
=
s
})
...
...
@@ -159,11 +143,14 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext {
updateState
()
if
(
Build
.
VERSION
.
SDK_INT
>=
21
&&
app
.
isNatEnabled
)
{
val
snackbar
=
Snackbar
.
make
(
findViewById
(
android
.
R
.
id
.
content
),
R
.
string
.
nat_deprecated
,
Snackbar
.
LENGTH_LONG
)
snackbar
.
setAction
(
R
.
string
.
switch_to_vpn
,
(
_
=>
preferences
.
natSwitch
.
setChecked
(
false
))
:
View.OnClickListene
r
)
addDisableNatToSnackbar
(
snackba
r
)
snackbar
.
show
()
}
}
private
def
addDisableNatToSnackbar
(
snackbar
:
Snackbar
)
=
snackbar
.
setAction
(
R
.
string
.
switch_to_vpn
,
(
_
=>
if
(
state
==
State
.
STOPPED
)
app
.
editor
.
putBoolean
(
Key
.
isNAT
,
false
))
:
View.OnClickListener
)
override
def
onServiceDisconnected
()
{
if
(
fab
!=
null
)
fab
.
setEnabled
(
false
)
}
...
...
@@ -184,10 +171,13 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext {
private
lazy
val
greyTint
=
ContextCompat
.
getColorStateList
(
this
,
R
.
color
.
material_blue_grey_700
)
private
lazy
val
greenTint
=
ContextCompat
.
getColorStateList
(
this
,
R
.
color
.
material_green_700
)
private
var
adView
:
AdView
=
_
private
lazy
val
preferences
=
getFragmentManager
.
findFragmentById
(
android
.
R
.
id
.
content
).
asInstanceOf
[
ShadowsocksSettings
]
val
handler
=
new
Handler
()
private
val
connectedListener
:
BroadcastReceiver
=
(
_
,
_
)
=>
if
(
app
.
isNatEnabled
)
connectionTestText
.
setVisibility
(
View
.
GONE
)
else
{
connectionTestText
.
setVisibility
(
View
.
VISIBLE
)
connectionTestText
.
setText
(
getString
(
R
.
string
.
connection_test_pending
))
}
private
def
changeSwitch
(
checked
:
Boolean
)
{
serviceStarted
=
checked
...
...
@@ -198,21 +188,6 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext {
}
}
private
def
showProgress
(
msg
:
Int
)
:
Handler
=
{
clearDialog
()
progressDialog
=
ProgressDialog
.
show
(
this
,
""
,
getString
(
msg
),
true
,
false
)
new
Handler
{
override
def
handleMessage
(
msg
:
Message
)
{
clearDialog
()
}
}
}
def
cancelStart
()
{
clearDialog
()
changeSwitch
(
checked
=
false
)
}
def
prepareStartService
()
{
Utils
.
ThrowableFuture
{
if
(
app
.
isNatEnabled
)
serviceLoad
()
else
{
...
...
@@ -227,27 +202,30 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext {
}
override
def
onCreate
(
savedInstanceState
:
Bundle
)
{
super
.
onCreate
(
savedInstanceState
)
setContentView
(
R
.
layout
.
layout_main
)
// Initialize Toolbar
val
toolbar
=
findViewById
(
R
.
id
.
toolbar
).
asInstanceOf
[
Toolbar
]
toolbar
.
setTitle
(
"shadowsocks"
)
// non-translatable logo
toolbar
.
setTitleTextAppearance
(
toolbar
.
getContext
,
R
.
style
.
Toolbar_Logo
)
val
field
=
classOf
[
Toolbar
].
getDeclaredField
(
"mTitleTextView"
)
field
.
setAccessible
(
true
)
val
title
=
field
.
get
(
toolbar
).
asInstanceOf
[
TextView
]
title
.
setFocusable
(
true
)
title
.
setGravity
(
0x10
)
title
.
getLayoutParams
.
height
=
ViewGroup
.
LayoutParams
.
MATCH_PARENT
title
.
setOnClickListener
(
_
=>
startActivity
(
new
Intent
(
this
,
classOf
[
ProfileManagerActivity
])))
val
typedArray
=
obtainStyledAttributes
(
Array
(
R
.
attr
.
selectableItemBackgroundBorderless
))
title
.
setBackgroundResource
(
typedArray
.
getResourceId
(
0
,
0
))
typedArray
.
recycle
()
val
tf
=
Typefaces
.
get
(
this
,
"fonts/Iceland.ttf"
)
if
(
tf
!=
null
)
title
.
setTypeface
(
tf
)
title
.
setCompoundDrawablesWithIntrinsicBounds
(
0
,
0
,
R
.
drawable
.
ic_arrow_drop_down
,
0
)
drawer
=
new
DrawerBuilder
()
.
withActivity
(
this
)
// TODO: .withHeader(R.drawable.some_header)
.
addDrawerItems
(
new
PrimaryDrawerItem
()
.
withIdentifier
(
DRAWER_PROFILES
)
.
withName
(
R
.
string
.
profiles
)
// TODO: withIcons
)
.
addStickyDrawerItems
(
new
SecondaryDrawerItem
()
.
withIdentifier
(
DRAWER_GLOBAL_SETTINGS
)
.
withName
(
R
.
string
.
settings
),
new
SecondaryDrawerItem
()
.
withIdentifier
(
DRAWER_ABOUT
)
.
withName
(
R
.
string
.
about
)
.
withSelectable
(
false
)
)
.
withOnDrawerItemClickListener
(
this
)
.
withActionBarDrawerToggle
(
true
)
.
build
()
if
(
savedInstanceState
==
null
)
displayFragment
(
profilesFragment
)
stat
=
findViewById
(
R
.
id
.
stat
)
connectionTestText
=
findViewById
(
R
.
id
.
connection_test
).
asInstanceOf
[
TextView
]
txText
=
findViewById
(
R
.
id
.
tx
).
asInstanceOf
[
TextView
]
...
...
@@ -297,8 +275,8 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext {
fab
=
findViewById
(
R
.
id
.
fab
).
asInstanceOf
[
FloatingActionButton
]
fabProgressCircle
=
findViewById
(
R
.
id
.
fabProgressCircle
).
asInstanceOf
[
FABProgressCircle
]
fab
.
setOnClickListener
(
_
=>
if
(
serviceStarted
)
serviceStop
()
else
if
(
bgService
!=
null
)
prepareStartService
()
else
changeSwitch
(
checked
=
false
))
else
if
(
bgService
!=
null
)
prepareStartService
()
else
changeSwitch
(
checked
=
false
))
fab
.
setOnLongClickListener
((
v
:
View
)
=>
{
Utils
.
positionToast
(
Toast
.
makeText
(
this
,
if
(
serviceStarted
)
R
.
string
.
stop
else
R
.
string
.
connect
,
Toast
.
LENGTH_SHORT
),
fab
,
getWindow
,
0
,
Utils
.
dpToPx
(
this
,
8
)).
show
()
...
...
@@ -307,6 +285,81 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext {
updateTraffic
(
0
,
0
,
0
,
0
)
handler
.
post
(
attachServiceCallback
)
app
.
settings
.
registerOnSharedPreferenceChangeListener
(
this
)
registerReceiver
(
connectedListener
,
new
IntentFilter
(
Action
.
CONNECTED
))
val
intent
=
getIntent
if
(
intent
!=
null
)
handleShareIntent
(
intent
)
}
override
def
onNewIntent
(
intent
:
Intent
)
{
super
.
onNewIntent
(
intent
)
handleShareIntent
(
intent
)
}
def
handleShareIntent
(
intent
:
Intent
)
{
val
sharedStr
=
intent
.
getAction
match
{
case
Intent
.
ACTION_VIEW
=>
intent
.
getData
.
toString
case
NfcAdapter
.
ACTION_NDEF_DISCOVERED
=>
val
rawMsgs
=
intent
.
getParcelableArrayExtra
(
NfcAdapter
.
EXTRA_NDEF_MESSAGES
)
if
(
rawMsgs
!=
null
&&
rawMsgs
.
nonEmpty
)
new
String
(
rawMsgs
(
0
).
asInstanceOf
[
NdefMessage
].
getRecords
()(
0
).
getPayload
)
else
null
case
_
=>
null
}
if
(
TextUtils
.
isEmpty
(
sharedStr
))
return
val
profiles
=
Parser
.
findAll
(
sharedStr
).
toList
if
(
profiles
.
isEmpty
)
{
// TODO: show error msg
return
}
val
dialog
=
new
AlertDialog
.
Builder
(
this
)
.
setTitle
(
R
.
string
.
add_profile_dialog
)
.
setPositiveButton
(
android
.
R
.
string
.
yes
,
((
_
,
_
)
=>
profiles
.
foreach
(
app
.
profileManager
.
createProfile
))
:
DialogInterface.OnClickListener
)
.
setNegativeButton
(
android
.
R
.
string
.
no
,
null
)
.
setMessage
(
profiles
.
mkString
(
"\n"
))
.
create
()
dialog
.
show
()
}
def
onSharedPreferenceChanged
(
pref
:
SharedPreferences
,
key
:
String
)
:
Unit
=
key
match
{
case
Key
.
isNAT
=>
handler
.
post
(()
=>
{
detachService
()
attachServiceCallback
()
})
case
_
=>
}
private
def
displayFragment
(
fragment
:
ToolbarFragment
)
{
getFragmentManager
.
beginTransaction
().
replace
(
R
.
id
.
content
,
fragment
).
commitAllowingStateLoss
()
drawer
.
closeDrawer
()
}
override
def
onItemClick
(
view
:
View
,
position
:
Int
,
drawerItem
:
IDrawerItem
[
_
,
_
<:
ViewHolder
])
:
Boolean
=
{
drawerItem
.
getIdentifier
match
{
case
DRAWER_PROFILES
=>
displayFragment
(
profilesFragment
)
case
DRAWER_GLOBAL_SETTINGS
=>
displayFragment
(
globalSettingsFragment
)
case
DRAWER_ABOUT
=>
app
.
track
(
TAG
,
"about"
)
val
web
=
new
WebView
(
this
)
web
.
loadUrl
(
"file:///android_asset/pages/about.html"
)
web
.
setWebViewClient
(
new
WebViewClient
()
{
override
def
shouldOverrideUrlLoading
(
view
:
WebView
,
url
:
String
)
:
Boolean
=
{
try
startActivity
(
new
Intent
(
Intent
.
ACTION_VIEW
,
Uri
.
parse
(
url
)))
catch
{
case
_:
android.content.ActivityNotFoundException
=>
// Ignore
}
true
}
})
new
AlertDialog
.
Builder
(
this
)
.
setTitle
(
getString
(
R
.
string
.
about_title
).
formatLocal
(
Locale
.
ENGLISH
,
BuildConfig
.
VERSION_NAME
))
.
setNegativeButton
(
getString
(
android
.
R
.
string
.
ok
),
null
)
.
setView
(
web
)
.
create
()
.
show
()
}
true
// unexpected cases will throw exception
}
private
def
hideCircle
()
{
...
...
@@ -317,97 +370,44 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext {
}
}
private
def
updateState
(
resetConnectionTest
:
Boolean
=
true
)
{
private
def
updateState
()
{
if
(
bgService
!=
null
)
{
bgService
.
getState
match
{
case
State
.
CONNECTING
=>
fab
.
setBackgroundTintList
(
greyTint
)
serviceStarted
=
false
fab
.
setImageResource
(
R
.
drawable
.
ic_start_busy
)
preferences
.
setEnabled
(
false
)
fabProgressCircle
.
show
()
stat
.
setVisibility
(
View
.
GONE
)
//
stat.setVisibility(View.GONE)
case
State
.
CONNECTED
=>
fab
.
setBackgroundTintList
(
greenTint
)
serviceStarted
=
true
fab
.
setImageResource
(
R
.
drawable
.
ic_start_connected
)
preferences
.
setEnabled
(
false
)
fabProgressCircle
.
postDelayed
(
hideCircle
,
100
)
stat
.
setVisibility
(
View
.
VISIBLE
)
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
fab
.
setImageResource
(
R
.
drawable
.
ic_start_busy
)
preferences
.
setEnabled
(
false
)
fabProgressCircle
.
show
()
stat
.
setVisibility
(
View
.
GONE
)
//
stat.setVisibility(View.GONE)
case
_
=>
fab
.
setBackgroundTintList
(
greyTint
)
serviceStarted
=
false
fab
.
setImageResource
(
R
.
drawable
.
ic_start_idle
)
preferences
.
setEnabled
(
true
)
fabProgressCircle
.
postDelayed
(
hideCircle
,
100
)
stat
.
setVisibility
(
View
.
GONE
)
//
stat.setVisibility(View.GONE)
}
state
=
bgService
.
getState
}
}
private
def
updateCurrentProfile
()
=
{
// Check if current profile changed
if
(
preferences
.
profile
==
null
||
app
.
profileId
!=
preferences
.
profile
.
id
)
{
updatePreferenceScreen
(
app
.
currentProfile
match
{
case
Some
(
profile
)
=>
profile
// updated
case
None
=>
// removed
app
.
switchProfile
((
app
.
profileManager
.
getFirstProfile
match
{
case
Some
(
first
)
=>
first
case
None
=>
app
.
profileManager
.
createDefault
()
}).
id
)
})
if
(
serviceStarted
)
serviceLoad
()
true
}
else
{
preferences
.
refreshProfile
()
false
}
}
protected
override
def
onResume
()
{
super
.
onResume
()
app
.
refreshContainerHolder
app
.
refreshContainerHolder
()
updateState
(
updateCurrentProfile
())
}
private
def
updatePreferenceScreen
(
profile
:
Profile
)
{
if
(
profile
.
host
==
"198.199.101.152"
)
if
(
adView
==
null
)
{
adView
=
new
AdView
(
this
)
adView
.
setAdUnitId
(
"ca-app-pub-9097031975646651/7760346322"
)
adView
.
setAdSize
(
AdSize
.
SMART_BANNER
)
preferences
.
getView
.
asInstanceOf
[
ViewGroup
].
addView
(
adView
,
1
)
// 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
)
updateState
()
}
override
def
onStart
()
{
...
...
@@ -417,30 +417,22 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext {
override
def
onStop
()
{
super
.
onStop
()
unregisterCallback
()
clearDialog
()
}
override
def
onDestroy
()
{
super
.
onDestroy
()
unregisterReceiver
(
connectedListener
)
app
.
settings
.
unregisterOnSharedPreferenceChangeListener
(
this
)
detachService
()
new
BackupManager
(
this
).
dataChanged
()
handler
.
removeCallbacksAndMessages
(
null
)
}
def
recovery
()
{
if
(
serviceStarted
)
serviceStop
()
val
h
=
showProgress
(
R
.
string
.
recovering
)
Utils
.
ThrowableFuture
{
app
.
copyAssets
()
h
.
sendEmptyMessage
(
0
)
}
}
override
def
onActivityResult
(
requestCode
:
Int
,
resultCode
:
Int
,
data
:
Intent
)
:
Unit
=
resultCode
match
{
case
Activity
.
RESULT_OK
=>
serviceLoad
()
case
_
=>
c
ancelStart
(
)
c
hangeSwitch
(
checked
=
false
)
Log
.
e
(
TAG
,
"Failed to start VpnService"
)
}
...
...
@@ -456,11 +448,4 @@ class Shadowsocks extends AppCompatActivity with ServiceBoundContext {
changeSwitch
(
checked
=
false
)
}
}
def
clearDialog
()
{
if
(
progressDialog
!=
null
&&
progressDialog
.
isShowing
)
{
if
(!
isDestroyed
)
progressDialog
.
dismiss
()
progressDialog
=
null
}
}
}
src/main/scala/com/github/shadowsocks/ProfileConfigActivity.scala
0 → 100644
View file @
dbb137c5
package
com.github.shadowsocks
import
android.app.Activity
import
android.os.Bundle
import
android.support.v7.widget.Toolbar
import
android.support.v7.widget.Toolbar.OnMenuItemClickListener
class
ProfileConfigActivity
extends
Activity
{
override
def
onCreate
(
savedInstanceState
:
Bundle
)
{
super
.
onCreate
(
savedInstanceState
)
setContentView
(
R
.
layout
.
layout_profile_config
)
val
toolbar
=
findViewById
(
R
.
id
.
toolbar
).
asInstanceOf
[
Toolbar
]
toolbar
.
setTitle
(
"Profile config"
)
// TODO
toolbar
.
setNavigationIcon
(
R
.
drawable
.
ic_navigation_close
)
toolbar
.
setNavigationOnClickListener
(
_
=>
finish
())
toolbar
.
inflateMenu
(
R
.
menu
.
profile_config_menu
)
toolbar
.
setOnMenuItemClickListener
(
getFragmentManager
.
findFragmentById
(
R
.
id
.
content
)
.
asInstanceOf
[
OnMenuItemClickListener
])
}
}
src/main/scala/com/github/shadowsocks/
widget/FloatingActionMenuBehavior
.scala
→
src/main/scala/com/github/shadowsocks/
ProfileConfigFragment
.scala
View file @
dbb137c5
...
...
@@ -18,68 +18,86 @@
/* */
/*******************************************************************************/
package
com.github.shadowsocks
.widget
package
com.github.shadowsocks
import
android.animation.ValueAnimator
import
android.content.Context
import
android.support.design.widget.CoordinatorLayout
import
android.support.design.widget.CoordinatorLayout.Behavior
import
android.support.design.widget.Snackbar.SnackbarLayout
import
android.support.v4.view.animation.FastOutSlowInInterpolator
import
android.util.AttributeSet
import
android.view.View
import
com.github.clans.fab.FloatingActionMenu
import
android.content._
import
android.os._
import
android.support.v14.preference.SwitchPreference
import
android.support.v4.content.LocalBroadcastManager
import
android.support.v7.app.AlertDialog
import
android.support.v7.preference.Preference
import
android.support.v7.widget.Toolbar.OnMenuItemClickListener
import
android.view.MenuItem
import
be.mygod.preference._
import
com.github.shadowsocks.ShadowsocksApplication.app
import
com.github.shadowsocks.database.Profile
import
com.github.shadowsocks.preferences.KcpCliPreferenceDialogFragment
import
com.github.shadowsocks.utils.
{
Action
,
Key
,
Utils
}
import
scala.collection.JavaConverters._
class
ProfileConfigFragment
extends
PreferenceFragment
with
OnMenuItemClickListener
{
private
lazy
val
clipboard
=
getActivity
.
getSystemService
(
Context
.
CLIPBOARD_SERVICE
).
asInstanceOf
[
ClipboardManager
]
private
var
profile
:
Profile
=
_
private
var
isProxyApps
:
SwitchPreference
=
_
/**
* Behavior for com.github.clans.fab.FloatingActionMenu that is aware of Snackbars and scrolling.
*
* @author Mygod
*/
class
FloatingActionMenuBehavior
(
context
:
Context
,
attrs
:
AttributeSet
)
extends
Behavior
[
FloatingActionMenu
](
context
,
attrs
)
{
private
var
fabTranslationYAnimator
:
ValueAnimator
=
_
private
var
fabTranslationY
:
Float
=
_
override
def
onCreatePreferences
(
bundle
:
Bundle
,
key
:
String
)
{
app
.
profileManager
.
getProfile
(
getActivity
.
getIntent
.
getIntExtra
(
Action
.
EXTRA_PROFILE_ID
,
-
1
))
match
{
case
Some
(
p
)
=>
profile
=
p
profile
.
serialize
(
app
.
editor
).
apply
()
case
None
=>
getActivity
.
finish
()
}
addPreferencesFromResource
(
R
.
xml
.
pref_profile
)
if
(
Build
.
VERSION
.
SDK_INT
>=
25
&&
getActivity
.
getSystemService
(
classOf
[
UserManager
]).
isDemoUser
)
{
findPreference
(
Key
.
host
).
setSummary
(
"shadowsocks.example.org"
)
findPreference
(
Key
.
remotePort
).
setSummary
(
"1337"
)
findPreference
(
Key
.
password
).
setSummary
(
"\u2022"
*
32
)
}
isProxyApps
=
findPreference
(
Key
.
proxyApps
).
asInstanceOf
[
SwitchPreference
]
isProxyApps
.
setEnabled
(
Utils
.
isLollipopOrAbove
||
app
.
isNatEnabled
)
isProxyApps
.
setOnPreferenceClickListener
(
_
=>
{
startActivity
(
new
Intent
(
getActivity
,
classOf
[
AppManager
]))
isProxyApps
.
setChecked
(
true
)
false
})
}
override
def
layoutDependsOn
(
parent
:
CoordinatorLayout
,
child
:
FloatingActionMenu
,
dependency
:
View
)
:
Boolean
=
dependency
.
isInstanceOf
[
SnackbarLayout
]
override
def
onResume
()
{
super
.
onResume
()
isProxyApps
.
setChecked
(
app
.
settings
.
getBoolean
(
Key
.
proxyApps
,
false
))
// fetch proxyApps updated by AppManager
}
override
def
onDependentViewChanged
(
parent
:
CoordinatorLayout
,
child
:
FloatingActionMenu
,
dependency
:
View
)
:
Boolean
=
{
dependency
match
{
case
_:
SnackbarLayout
=>
var
targetTransY
=
parent
.
getDependencies
(
child
).
asScala
.
filter
(
view
=>
view
.
isInstanceOf
[
SnackbarLayout
]
&&
parent
.
doViewsOverlap
(
child
,
view
))
.
map
(
view
=>
view
.
getTranslationY
-
view
.
getHeight
).
reduceOption
(
_
min
_
).
getOrElse
(
0F
)
if
(
targetTransY
>
0
)
targetTransY
=
0
if
(
fabTranslationY
!=
targetTransY
)
{
val
currentTransY
=
child
.
getTranslationY
if
(
fabTranslationYAnimator
!=
null
&&
fabTranslationYAnimator
.
isRunning
)
fabTranslationYAnimator
.
cancel
()
if
(
child
.
isShown
&&
Math
.
abs
(
currentTransY
-
targetTransY
)
>
child
.
getHeight
*
0.667F
)
{
if
(
fabTranslationYAnimator
==
null
)
{
fabTranslationYAnimator
=
new
ValueAnimator
fabTranslationYAnimator
.
setInterpolator
(
new
FastOutSlowInInterpolator
)
fabTranslationYAnimator
.
addUpdateListener
(
animation
=>
child
.
setTranslationY
(
animation
.
getAnimatedValue
.
asInstanceOf
[
Float
]))
}
fabTranslationYAnimator
.
setFloatValues
(
currentTransY
,
targetTransY
)
fabTranslationYAnimator
.
start
()
}
else
child
.
setTranslationY
(
targetTransY
)
fabTranslationY
=
targetTransY
}
}
false
override
def
onDisplayPreferenceDialog
(
preference
:
Preference
)
:
Unit
=
preference
.
getKey
match
{
case
Key
.
kcpcli
=>
displayPreferenceDialog
(
Key
.
kcpcli
,
new
KcpCliPreferenceDialogFragment
())
case
_
=>
super
.
onDisplayPreferenceDialog
(
preference
)
}
override
def
onStartNestedScroll
(
parent
:
CoordinatorLayout
,
child
:
FloatingActionMenu
,
directTargetChild
:
View
,
target
:
View
,
nestedScrollAxes
:
Int
)
=
true
override
def
onNestedScroll
(
parent
:
CoordinatorLayout
,
child
:
FloatingActionMenu
,
target
:
View
,
dxConsumed
:
Int
,
dyConsumed
:
Int
,
dxUnconsumed
:
Int
,
dyUnconsumed
:
Int
)
{
super
.
onNestedScroll
(
parent
,
child
,
target
,
dxConsumed
,
dyConsumed
,
dxUnconsumed
,
dyUnconsumed
)
val
dy
=
dyConsumed
+
dyUnconsumed
if
(
child
.
isMenuButtonHidden
)
{
if
(
dy
<
0
)
child
.
showMenuButton
(
true
)
}
else
if
(
dy
>
0
)
child
.
hideMenuButton
(
true
)
override
def
onMenuItemClick
(
item
:
MenuItem
)
:
Boolean
=
item
.
getItemId
match
{
case
R
.
id
.
action_qr_code_nfc
=>
getFragmentManager
.
beginTransaction
().
add
(
new
QRCodeDialog
(
profile
.
toString
),
""
).
commitAllowingStateLoss
()
true
case
R
.
id
.
action_export
=>
clipboard
.
setPrimaryClip
(
ClipData
.
newPlainText
(
null
,
profile
.
toString
))
true
case
R
.
id
.
action_delete
=>
new
AlertDialog
.
Builder
(
getActivity
)
.
setTitle
(
"Confirm?"
)
// TODO
.
setPositiveButton
(
android
.
R
.
string
.
yes
,
((
_
,
_
)
=>
{
app
.
profileManager
.
delProfile
(
profile
.
id
)
LocalBroadcastManager
.
getInstance
(
getActivity
)
.
sendBroadcast
(
new
Intent
(
Action
.
PROFILE_REMOVED
).
putExtra
(
Action
.
EXTRA_PROFILE_ID
,
profile
.
id
))
getActivity
.
finish
()
})
:
DialogInterface.OnClickListener
)
.
setNegativeButton
(
android
.
R
.
string
.
no
,
null
)
.
create
()
.
show
()
true
case
R
.
id
.
action_apply
=>
profile
.
deserialize
(
app
.
settings
)
app
.
profileManager
.
updateProfile
(
profile
)
LocalBroadcastManager
.
getInstance
(
getActivity
)
.
sendBroadcast
(
new
Intent
(
Action
.
PROFILE_CHANGED
).
putExtra
(
Action
.
EXTRA_PROFILE_ID
,
profile
.
id
))
getActivity
.
finish
()
true
case
_
=>
false
}
}
src/main/scala/com/github/shadowsocks/Profile
ManagerActivity
.scala
→
src/main/scala/com/github/shadowsocks/Profile
sFragment
.scala
View file @
dbb137c5
...
...
@@ -20,131 +20,78 @@
package
com.github.shadowsocks
import
java.nio.charset.Charset
import
android.app.TaskStackBuilder
import
android.app.Activity
import
android.content._
import
android.content.pm.PackageManager
import
android.nfc.NfcAdapter.CreateNdefMessageCallback
import
android.nfc.
{
NdefMessage
,
NdefRecord
,
NfcAdapter
,
NfcEvent
}
import
android.graphics.Typeface
import
android.os.
{
Build
,
Bundle
,
Handler
,
UserManager
}
import
android.provider.Settings
import
android.support.v7.app.
{
AlertDialog
,
AppCompatActivity
}
import
android.support.v4.content.LocalBroadcastManager
import
android.support.v7.widget.RecyclerView.ViewHolder
import
android.support.v7.widget.Toolbar.OnMenuItemClickListener
import
android.support.v7.widget._
import
android.support.v7.widget.helper.ItemTouchHelper
import
android.support.v7.widget.helper.ItemTouchHelper.SimpleCallback
import
android.text.style.TextAppearanceSpan
import
android.text.
{
SpannableStringBuilder
,
Spanned
,
TextUtils
}
import
android.view._
import
android.widget.
{
CheckedTextView
,
ImageView
,
LinearLayout
,
Toast
}
import
com.github.clans.fab.
{
FloatingActionButton
,
FloatingActionMenu
}
import
android.widget.
{
TextView
,
Toast
}
import
com.github.shadowsocks.ShadowsocksApplication.app
import
com.github.shadowsocks.aidl.IShadowsocksServiceCallback
import
com.github.shadowsocks.database.Profile
import
com.github.shadowsocks.utils.
{
Key
,
Parser
,
TrafficMonitor
,
Utils
}
import
com.github.shadowsocks.utils.
{
Action
,
Parser
,
TrafficMonitor
,
Utils
}
import
com.github.shadowsocks.widget.UndoSnackbarManager
import
net.glxn.qrgen.android.QRCode
import
scala.collection.mutable.ArrayBuffer
final
class
ProfileManagerActivity
extends
AppCompatActivity
with
OnMenuItemClickListener
with
ServiceBoundContext
with
View
.
OnClickListener
with
CreateNdefMessageCallback
{
final
class
ProfilesFragment
extends
ToolbarFragment
with
OnMenuItemClickListener
{
private
final
class
ProfileViewHolder
(
val
view
:
View
)
extends
RecyclerView
.
ViewHolder
(
view
)
with
View
.
OnClickListener
with
View
.
OnKeyListener
{
with
View
.
OnClickListener
{
var
item
:
Profile
=
_
private
val
text
=
itemView
.
findViewById
(
android
.
R
.
id
.
text1
).
asInstanceOf
[
CheckedTextView
]
private
val
text1
=
itemView
.
findViewById
(
android
.
R
.
id
.
text1
).
asInstanceOf
[
TextView
]
private
val
text2
=
itemView
.
findViewById
(
android
.
R
.
id
.
text2
).
asInstanceOf
[
TextView
]
itemView
.
setOnClickListener
(
this
)
itemView
.
setOnKeyListener
(
this
)
{
val
shareBtn
=
itemView
.
findViewById
(
R
.
id
.
share
)
shareBtn
.
setOnClickListener
(
_
=>
{
val
url
=
item
.
toString
if
(
isNfcBeamEnabled
)
{
nfcAdapter
.
setNdefPushMessageCallback
(
ProfileManagerActivity
.
this
,
ProfileManagerActivity
.
this
)
nfcShareItem
=
url
.
getBytes
(
Charset
.
forName
(
"UTF-8"
))
}
val
image
=
new
ImageView
(
ProfileManagerActivity
.
this
)
image
.
setLayoutParams
(
new
LinearLayout
.
LayoutParams
(-
1
,
-
1
))
val
qrcode
=
QRCode
.
from
(
url
)
.
withSize
(
Utils
.
dpToPx
(
ProfileManagerActivity
.
this
,
250
),
Utils
.
dpToPx
(
ProfileManagerActivity
.
this
,
250
))
.
asInstanceOf
[
QRCode
].
bitmap
()
image
.
setImageBitmap
(
qrcode
)
val
dialog
=
new
AlertDialog
.
Builder
(
ProfileManagerActivity
.
this
)
.
setCancelable
(
true
)
.
setPositiveButton
(
R
.
string
.
close
,
null
)
.
setNegativeButton
(
R
.
string
.
copy_url
,
((
_
,
_
)
=>
clipboard
.
setPrimaryClip
(
ClipData
.
newPlainText
(
null
,
url
)))
:
DialogInterface.OnClickListener
)
.
setView
(
image
)
.
setTitle
(
R
.
string
.
share
)
.
create
()
if
(!
isNfcAvailable
)
dialog
.
setMessage
(
getString
(
R
.
string
.
share_message_without_nfc
))
else
if
(!
isNfcBeamEnabled
)
{
dialog
.
setMessage
(
getString
(
R
.
string
.
share_message_nfc_disabled
))
dialog
.
setButton
(
DialogInterface
.
BUTTON_NEUTRAL
,
getString
(
R
.
string
.
turn_on_nfc
),
((
_
,
_
)
=>
startActivity
(
new
Intent
(
Settings
.
ACTION_NFC_SETTINGS
)))
:
DialogInterface.OnClickListener
)
}
else
{
dialog
.
setMessage
(
getString
(
R
.
string
.
share_message
))
dialog
.
setOnDismissListener
(
_
=>
nfcAdapter
.
setNdefPushMessageCallback
(
null
,
ProfileManagerActivity
.
this
))
}
dialog
.
show
()
})
shareBtn
.
setOnLongClickListener
(
_
=>
{
Utils
.
positionToast
(
Toast
.
makeText
(
ProfileManagerActivity
.
this
,
R
.
string
.
share
,
Toast
.
LENGTH_SHORT
),
shareBtn
,
getWindow
,
0
,
Utils
.
dpToPx
(
ProfileManagerActivity
.
this
,
8
)).
show
()
val
edit
=
itemView
.
findViewById
(
R
.
id
.
edit
)
edit
.
setOnClickListener
(
_
=>
startActivity
(
new
Intent
(
getActivity
,
classOf
[
ProfileConfigActivity
])
.
putExtra
(
Action
.
EXTRA_PROFILE_ID
,
item
.
id
)))
edit
.
setOnLongClickListener
(
_
=>
{
Utils
.
positionToast
(
Toast
.
makeText
(
getActivity
,
edit
.
getContentDescription
,
Toast
.
LENGTH_SHORT
),
edit
,
getActivity
.
getWindow
,
0
,
Utils
.
dpToPx
(
getActivity
,
8
)).
show
()
true
})
}
def
updateText
(
txTotal
:
Long
=
0
,
rxTotal
:
Long
=
0
)
{
val
builder
=
new
SpannableStringBuilder
val
tx
=
item
.
tx
+
txTotal
val
rx
=
item
.
rx
+
rxTotal
builder
.
append
(
if
(
isDemoMode
)
"Profile #"
+
item
.
id
else
item
.
name
)
var
title
=
if
(
isDemoMode
)
"Profile #"
+
item
.
id
else
item
.
name
val
builder
=
new
StringBuilder
()
val
address
=
(
if
(
item
.
host
.
contains
(
":"
))
"[%s]:%d"
else
"%s:%d"
).
format
(
item
.
host
,
item
.
remotePort
)
if
(
title
==
null
||
title
.
isEmpty
)
title
=
address
else
builder
.
append
(
address
)
if
(
tx
!=
0
||
rx
!=
0
)
{
val
start
=
builder
.
length
if
(
builder
.
nonEmpty
)
builder
.
append
(
'\n'
)
builder
.
append
(
getString
(
R
.
string
.
stat_profiles
,
TrafficMonitor
.
formatTraffic
(
tx
),
TrafficMonitor
.
formatTraffic
(
rx
)))
builder
.
setSpan
(
new
TextAppearanceSpan
(
ProfileManagerActivity
.
this
,
android
.
R
.
style
.
TextAppearance_Small
),
start
+
1
,
builder
.
length
,
Spanned
.
SPAN_EXCLUSIVE_EXCLUSIVE
)
}
handler
.
post
(()
=>
text
.
setText
(
builder
))
handler
.
post
(()
=>
{
text1
.
setText
(
title
)
text2
.
setText
(
builder
)
text2
.
setVisibility
(
if
(
builder
.
isEmpty
)
View
.
GONE
else
View
.
VISIBLE
)
})
}
def
bind
(
item
:
Profile
)
{
this
.
item
=
item
updateText
()
if
(
item
.
id
==
app
.
profileId
)
{
text
.
setChecked
(
true
)
text
1
.
setTypeface
(
null
,
Typeface
.
BOLD
)
selectedItem
=
this
}
else
{
text
.
setChecked
(
false
)
text
1
.
setTypeface
(
null
,
Typeface
.
NORMAL
)
if
(
selectedItem
eq
this
)
selectedItem
=
null
}
}
def
onClick
(
v
:
View
)
{
app
.
switchProfile
(
item
.
id
)
finish
()
}
def
onKey
(
v
:
View
,
keyCode
:
Int
,
event
:
KeyEvent
)
:
Boolean
=
if
(
event
.
getAction
==
KeyEvent
.
ACTION_DOWN
)
keyCode
match
{
case
KeyEvent
.
KEYCODE_DPAD_LEFT
=>
val
index
=
getAdapterPosition
if
(
index
>=
0
)
{
profilesAdapter
.
remove
(
index
)
undoManager
.
remove
(
index
,
item
)
true
}
else
false
case
_
=>
false
}
else
false
def
onClick
(
v
:
View
)
:
Unit
=
app
.
switchProfile
(
item
.
id
)
}
private
class
ProfilesAdapter
extends
RecyclerView
.
Adapter
[
ProfileViewHolder
]
{
...
...
@@ -196,46 +143,48 @@ final class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClic
app
.
profileManager
.
delProfile
(
item
.
id
)
if
(
item
.
id
==
app
.
profileId
)
app
.
profileId
(-
1
)
}
def
refreshId
(
id
:
Int
)
{
val
index
=
profiles
.
indexWhere
(
_
.
id
==
id
)
profiles
(
index
)
=
app
.
profileManager
.
getProfile
(
id
).
get
notifyItemChanged
(
index
)
}
def
removeId
(
id
:
Int
)
{
val
index
=
profiles
.
indexWhere
(
_
.
id
==
id
)
profiles
.
remove
(
index
)
notifyItemRemoved
(
index
)
}
}
private
var
selectedItem
:
ProfileViewHolder
=
_
private
val
handler
=
new
Handler
private
var
menu
:
FloatingActionMenu
=
_
private
val
profilesListener
:
BroadcastReceiver
=
(
_
,
intent
)
=>
{
val
id
=
intent
.
getIntExtra
(
Action
.
EXTRA_PROFILE_ID
,
-
1
)
intent
.
getAction
match
{
case
Action
.
PROFILE_CHANGED
=>
profilesAdapter
.
refreshId
(
id
)
case
Action
.
PROFILE_REMOVED
=>
profilesAdapter
.
removeId
(
id
)
}
}
private
lazy
val
profilesAdapter
=
new
ProfilesAdapter
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
]
=
_
private
var
isNfcAvailable
:
Boolean
=
_
private
var
isNfcEnabled
:
Boolean
=
_
private
var
isNfcBeamEnabled
:
Boolean
=
_
private
lazy
val
clipboard
=
getActivity
.
getSystemService
(
Context
.
CLIPBOARD_SERVICE
).
asInstanceOf
[
ClipboardManager
]
private
lazy
val
isDemoMode
=
Build
.
VERSION
.
SDK_INT
>=
25
&&
getActivity
.
getSystemService
(
classOf
[
UserManager
]).
isDemoUser
override
def
onCreate
(
savedInstanceState
:
Bundle
)
{
super
.
onCreate
(
savedInstanceState
)
setContentView
(
R
.
layout
.
layout_profiles
)
override
def
onCreateView
(
inflater
:
LayoutInflater
,
container
:
ViewGroup
,
savedInstanceState
:
Bundle
)
:
View
=
inflater
.
inflate
(
R
.
layout
.
layout_profiles
,
container
,
false
)
val
toolbar
=
findViewById
(
R
.
id
.
toolbar
).
asInstanceOf
[
Toolbar
]
override
def
onViewCreated
(
view
:
View
,
savedInstanceState
:
Bundle
)
{
super
.
onViewCreated
(
view
,
savedInstanceState
)
toolbar
.
setTitle
(
R
.
string
.
profiles
)
toolbar
.
setNavigationIcon
(
R
.
drawable
.
ic_navigation_close
)
toolbar
.
setNavigationOnClickListener
(
_
=>
{
val
intent
=
getParentActivityIntent
if
(
shouldUpRecreateTask
(
intent
)
||
isTaskRoot
)
TaskStackBuilder
.
create
(
this
).
addNextIntentWithParentStack
(
intent
).
startActivities
()
else
finish
()
})
toolbar
.
inflateMenu
(
R
.
menu
.
profile_manager_menu
)
toolbar
.
setOnMenuItemClickListener
(
this
)
initFab
()
app
.
profileManager
.
setProfileAddedListener
(
profilesAdapter
.
add
)
val
profilesList
=
findViewById
(
R
.
id
.
profilesList
).
asInstanceOf
[
RecyclerView
]
val
layoutManager
=
new
LinearLayoutManager
(
this
)
val
profilesList
=
view
.
findViewById
(
R
.
id
.
profilesList
).
asInstanceOf
[
RecyclerView
]
val
layoutManager
=
new
LinearLayoutManager
(
getActivity
)
profilesList
.
setLayoutManager
(
layoutManager
)
profilesList
.
setItemAnimator
(
new
DefaultItemAnimator
)
profilesList
.
setAdapter
(
profilesAdapter
)
...
...
@@ -255,169 +204,65 @@ final class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClic
true
}
}).
attachToRecyclerView
(
profilesList
)
val
filter
=
new
IntentFilter
(
Action
.
PROFILE_CHANGED
)
filter
.
addAction
(
Action
.
PROFILE_REMOVED
)
LocalBroadcastManager
.
getInstance
(
getActivity
).
registerReceiver
(
profilesListener
,
filter
)
}
attachService
(
new
IShadowsocksServiceCallback
.
Stub
{
override
def
onAttach
(
activity
:
Activity
)
{
super
.
onAttach
(
activity
)
activity
.
asInstanceOf
[
ServiceBoundContext
].
attachService
(
new
IShadowsocksServiceCallback
.
Stub
{
def
stateChanged
(
state
:
Int
,
profileName
:
String
,
msg
:
String
)
:
Unit
=
()
// ignore
def
trafficUpdated
(
txRate
:
Long
,
rxRate
:
Long
,
txTotal
:
Long
,
rxTotal
:
Long
)
:
Unit
=
if
(
selectedItem
!=
null
)
selectedItem
.
updateText
(
txTotal
,
rxTotal
)
})
if
(
app
.
settings
.
getBoolean
(
Key
.
profileTip
,
true
))
{
app
.
editor
.
putBoolean
(
Key
.
profileTip
,
false
).
apply
()
new
AlertDialog
.
Builder
(
this
).
setTitle
(
R
.
string
.
profile_manager_dialog
)
.
setMessage
(
R
.
string
.
profile_manager_dialog_content
).
setPositiveButton
(
R
.
string
.
gotcha
,
null
).
create
().
show
()
}
val
intent
=
getIntent
if
(
intent
!=
null
)
handleShareIntent
(
intent
)
}
def
initFab
()
{
menu
=
findViewById
(
R
.
id
.
menu
).
asInstanceOf
[
FloatingActionMenu
]
menu
.
setClosedOnTouchOutside
(
true
)
val
dm
=
AppCompatDrawableManager
.
get
val
manualAddFAB
=
findViewById
(
R
.
id
.
fab_manual_add
).
asInstanceOf
[
FloatingActionButton
]
manualAddFAB
.
setImageDrawable
(
dm
.
getDrawable
(
this
,
R
.
drawable
.
ic_content_create
))
manualAddFAB
.
setOnClickListener
(
this
)
val
qrcodeAddFAB
=
findViewById
(
R
.
id
.
fab_qrcode_add
).
asInstanceOf
[
FloatingActionButton
]
qrcodeAddFAB
.
setImageDrawable
(
dm
.
getDrawable
(
this
,
R
.
drawable
.
ic_image_camera_alt
))
qrcodeAddFAB
.
setOnClickListener
(
this
)
val
nfcAddFAB
=
findViewById
(
R
.
id
.
fab_nfc_add
).
asInstanceOf
[
FloatingActionButton
]
nfcAddFAB
.
setImageDrawable
(
dm
.
getDrawable
(
this
,
R
.
drawable
.
ic_device_nfc
))
nfcAddFAB
.
setOnClickListener
(
this
)
val
importAddFAB
=
findViewById
(
R
.
id
.
fab_import_add
).
asInstanceOf
[
FloatingActionButton
]
importAddFAB
.
setImageDrawable
(
dm
.
getDrawable
(
this
,
R
.
drawable
.
ic_content_paste
))
importAddFAB
.
setOnClickListener
(
this
)
menu
.
setOnMenuToggleListener
(
opened
=>
if
(
opened
)
qrcodeAddFAB
.
setVisibility
(
if
(
getPackageManager
.
hasSystemFeature
(
PackageManager
.
FEATURE_CAMERA
))
View
.
VISIBLE
else
View
.
GONE
))
}
override
def
onResume
()
{
super
.
onResume
()
updateNfcState
()
}
override
def
onNewIntent
(
intent
:
Intent
)
{
super
.
onNewIntent
(
intent
)
handleShareIntent
(
intent
)
}
override
def
onClick
(
v
:
View
){
v
.
getId
match
{
case
R
.
id
.
fab_manual_add
=>
menu
.
toggle
(
true
)
val
profile
=
app
.
profileManager
.
createProfile
()
app
.
profileManager
.
updateProfile
(
profile
)
app
.
switchProfile
(
profile
.
id
)
finish
()
case
R
.
id
.
fab_qrcode_add
=>
menu
.
toggle
(
false
)
val
intent
=
new
Intent
(
this
,
classOf
[
ScannerActivity
])
startActivity
(
intent
)
case
R
.
id
.
fab_nfc_add
=>
menu
.
toggle
(
true
)
val
dialog
=
new
AlertDialog
.
Builder
(
ProfileManagerActivity
.
this
)
.
setCancelable
(
true
)
.
setPositiveButton
(
R
.
string
.
gotcha
,
null
)
.
setTitle
(
R
.
string
.
add_profile_nfc_hint_title
)
.
create
()
if
(!
isNfcBeamEnabled
)
{
dialog
.
setMessage
(
getString
(
R
.
string
.
share_message_nfc_disabled
))
dialog
.
setButton
(
DialogInterface
.
BUTTON_NEUTRAL
,
getString
(
R
.
string
.
turn_on_nfc
),
((
_
,
_
)
=>
startActivity
(
new
Intent
(
Settings
.
ACTION_NFC_SETTINGS
))
)
:
DialogInterface.OnClickListener
)
}
else
{
dialog
.
setMessage
(
getString
(
R
.
string
.
add_profile_nfc_hint
))
}
dialog
.
show
()
case
R
.
id
.
fab_import_add
=>
menu
.
toggle
(
true
)
if
(
clipboard
.
hasPrimaryClip
)
{
val
profiles
=
Parser
.
findAll
(
clipboard
.
getPrimaryClip
.
getItemAt
(
0
).
getText
)
if
(
profiles
.
nonEmpty
)
{
profiles
.
foreach
(
app
.
profileManager
.
createProfile
)
Toast
.
makeText
(
this
,
R
.
string
.
action_import_msg
,
Toast
.
LENGTH_SHORT
).
show
()
return
}
}
Toast
.
makeText
(
this
,
R
.
string
.
action_import_err
,
Toast
.
LENGTH_SHORT
).
show
()
}
}
def
updateNfcState
()
{
isNfcAvailable
=
false
isNfcEnabled
=
false
isNfcBeamEnabled
=
false
nfcAdapter
=
NfcAdapter
.
getDefaultAdapter
(
this
)
if
(
nfcAdapter
!=
null
)
{
isNfcAvailable
=
true
if
(
nfcAdapter
.
isEnabled
)
{
isNfcEnabled
=
true
if
(
nfcAdapter
.
isNdefPushEnabled
)
{
isNfcBeamEnabled
=
true
nfcAdapter
.
setNdefPushMessageCallback
(
null
,
ProfileManagerActivity
.
this
)
}
}
}
}
def
handleShareIntent
(
intent
:
Intent
)
{
val
sharedStr
=
intent
.
getAction
match
{
case
Intent
.
ACTION_VIEW
=>
intent
.
getData
.
toString
case
NfcAdapter
.
ACTION_NDEF_DISCOVERED
=>
val
rawMsgs
=
intent
.
getParcelableArrayExtra
(
NfcAdapter
.
EXTRA_NDEF_MESSAGES
)
if
(
rawMsgs
!=
null
&&
rawMsgs
.
nonEmpty
)
new
String
(
rawMsgs
(
0
).
asInstanceOf
[
NdefMessage
].
getRecords
()(
0
).
getPayload
)
else
null
case
_
=>
null
}
if
(
TextUtils
.
isEmpty
(
sharedStr
))
return
val
profiles
=
Parser
.
findAll
(
sharedStr
).
toList
if
(
profiles
.
isEmpty
)
{
finish
()
return
}
val
dialog
=
new
AlertDialog
.
Builder
(
this
)
.
setTitle
(
R
.
string
.
add_profile_dialog
)
.
setPositiveButton
(
android
.
R
.
string
.
yes
,
((
_
,
_
)
=>
profiles
.
foreach
(
app
.
profileManager
.
createProfile
))
:
DialogInterface.OnClickListener
)
.
setNegativeButton
(
android
.
R
.
string
.
no
,
((
_
,
_
)
=>
finish
())
:
DialogInterface.OnClickListener
)
.
setMessage
(
profiles
.
mkString
(
"\n"
))
.
create
()
dialog
.
show
()
}
override
def
onStart
()
{
super
.
onStart
()
registerCallback
getActivity
.
asInstanceOf
[
ServiceBoundContext
].
registerCallback
()
}
override
def
onStop
()
{
super
.
onStop
()
unregisterCallback
getActivity
.
asInstanceOf
[
ServiceBoundContext
].
unregisterCallback
()
}
override
def
onDe
stroy
()
{
detachService
()
override
def
onDe
tach
()
{
getActivity
.
asInstanceOf
[
ServiceBoundContext
].
detachService
()
undoManager
.
flush
()
app
.
profileManager
.
setProfileAddedListener
(
null
)
super
.
onDestroy
()
super
.
onDetach
()
}
override
def
onBackPressed
()
{
if
(
menu
.
isOpened
)
menu
.
close
(
true
)
else
super
.
onBackPressed
()
override
def
onDestroy
()
{
LocalBroadcastManager
.
getInstance
(
getActivity
).
unregisterReceiver
(
profilesListener
)
super
.
onDestroy
()
}
def
createNdefMessage
(
nfcEvent
:
NfcEvent
)
=
new
NdefMessage
(
Array
(
new
NdefRecord
(
NdefRecord
.
TNF_ABSOLUTE_URI
,
nfcShareItem
,
Array
[
Byte
](),
nfcShareItem
)))
def
onMenuItemClick
(
item
:
MenuItem
)
:
Boolean
=
item
.
getItemId
match
{
case
R
.
id
.
action_scan_qr_code
=>
startActivity
(
new
Intent
(
getActivity
,
classOf
[
ScannerActivity
]))
true
case
R
.
id
.
action_import
=>
if
(
clipboard
.
hasPrimaryClip
)
{
val
profiles
=
Parser
.
findAll
(
clipboard
.
getPrimaryClip
.
getItemAt
(
0
).
getText
)
if
(
profiles
.
nonEmpty
)
{
profiles
.
foreach
(
app
.
profileManager
.
createProfile
)
Toast
.
makeText
(
getActivity
,
R
.
string
.
action_import_msg
,
Toast
.
LENGTH_SHORT
).
show
()
return
true
}
}
Toast
.
makeText
(
getActivity
,
R
.
string
.
action_import_err
,
Toast
.
LENGTH_SHORT
).
show
()
true
case
R
.
id
.
action_manual_settings
=>
val
profile
=
app
.
profileManager
.
createProfile
()
app
.
profileManager
.
updateProfile
(
profile
)
case
R
.
id
.
action_export
=>
app
.
profileManager
.
getAllProfiles
match
{
case
Some
(
profiles
)
=>
clipboard
.
setPrimaryClip
(
ClipData
.
newPlainText
(
null
,
profiles
.
mkString
(
"\n"
)))
Toast
.
makeText
(
this
,
R
.
string
.
action_export_msg
,
Toast
.
LENGTH_SHORT
).
show
()
case
_
=>
Toast
.
makeText
(
this
,
R
.
string
.
action_export_err
,
Toast
.
LENGTH_SHORT
).
show
()
Toast
.
makeText
(
getActivity
,
R
.
string
.
action_export_msg
,
Toast
.
LENGTH_SHORT
).
show
()
case
_
=>
Toast
.
makeText
(
getActivity
,
R
.
string
.
action_export_err
,
Toast
.
LENGTH_SHORT
).
show
()
}
true
case
_
=>
false
...
...
src/main/scala/com/github/shadowsocks/QRCodeDialog.scala
0 → 100644
View file @
dbb137c5
package
com.github.shadowsocks
import
java.nio.charset.Charset
import
android.app.Activity
import
android.nfc.
{
NdefMessage
,
NdefRecord
,
NfcAdapter
}
import
android.os.Bundle
import
android.view.
{
LayoutInflater
,
View
,
ViewGroup
}
import
android.widget.
{
ImageView
,
LinearLayout
}
import
com.github.shadowsocks.utils.Utils
import
net.glxn.qrgen.android.QRCode
final
class
QRCodeDialog
(
url
:
String
)
extends
DialogFragment
{
private
lazy
val
nfcShareItem
=
url
.
getBytes
(
Charset
.
forName
(
"UTF-8"
))
private
var
adapter
:
NfcAdapter
=
_
override
def
onCreateView
(
inflater
:
LayoutInflater
,
container
:
ViewGroup
,
savedInstanceState
:
Bundle
)
:
View
=
{
val
image
=
new
ImageView
(
getActivity
)
image
.
setLayoutParams
(
new
LinearLayout
.
LayoutParams
(-
1
,
-
1
))
val
qrcode
=
QRCode
.
from
(
url
)
.
withSize
(
Utils
.
dpToPx
(
getActivity
,
250
),
Utils
.
dpToPx
(
getActivity
,
250
))
.
asInstanceOf
[
QRCode
].
bitmap
()
image
.
setImageBitmap
(
qrcode
)
image
}
override
def
onAttach
(
activity
:
Activity
)
{
superOnAttach
(
activity
)
adapter
=
NfcAdapter
.
getDefaultAdapter
(
getActivity
)
if
(
adapter
!=
null
)
adapter
.
setNdefPushMessage
(
new
NdefMessage
(
Array
(
new
NdefRecord
(
NdefRecord
.
TNF_ABSOLUTE_URI
,
nfcShareItem
,
Array
[
Byte
](),
nfcShareItem
))),
activity
)
}
override
def
onDetach
()
{
if
(
adapter
!=
null
)
{
adapter
.
setNdefPushMessage
(
null
,
getActivity
)
adapter
=
null
}
super
.
onDetach
()
}
}
src/main/scala/com/github/shadowsocks/ServiceBoundContext.scala
View file @
dbb137c5
...
...
@@ -46,14 +46,14 @@ trait ServiceBoundContext extends Context with IBinder.DeathRecipient {
}
}
protected
def
registerCallback
()
:
Unit
=
if
(
bgService
!=
null
&&
callback
!=
null
&&
!
callbackRegistered
)
try
{
def
registerCallback
()
:
Unit
=
if
(
bgService
!=
null
&&
callback
!=
null
&&
!
callbackRegistered
)
try
{
bgService
.
registerCallback
(
callback
)
callbackRegistered
=
true
}
catch
{
case
_:
RemoteException
=>
// Nothing
}
protected
def
unregisterCallback
()
{
def
unregisterCallback
()
{
if
(
bgService
!=
null
&&
callback
!=
null
&&
callbackRegistered
)
try
bgService
.
unregisterCallback
(
callback
)
catch
{
case
_:
RemoteException
=>
}
...
...
src/main/scala/com/github/shadowsocks/ShadowsocksNotification.scala
View file @
dbb137c5
...
...
@@ -57,7 +57,7 @@ class ShadowsocksNotification(private val service: BaseService, profileName: Str
.
setColor
(
ContextCompat
.
getColor
(
service
,
R
.
color
.
material_primary_500
))
.
setTicker
(
service
.
getString
(
R
.
string
.
forward_success
))
.
setContentTitle
(
profileName
)
.
setContentIntent
(
PendingIntent
.
getActivity
(
service
,
0
,
new
Intent
(
service
,
classOf
[
Shadowsocks
])
.
setContentIntent
(
PendingIntent
.
getActivity
(
service
,
0
,
new
Intent
(
service
,
classOf
[
MainActivity
])
.
setFlags
(
Intent
.
FLAG_ACTIVITY_REORDER_TO_FRONT
),
0
))
.
setSmallIcon
(
R
.
drawable
.
ic_stat_shadowsocks
)
builder
.
addAction
(
R
.
drawable
.
ic_navigation_close
,
...
...
src/main/scala/com/github/shadowsocks/ShadowsocksSettings.scala
deleted
100644 → 0
View file @
7eddf8be
/*******************************************************************************/
/* */
/* Copyright (C) 2016 by Max Lv <max.c.lv@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 */
/* the Free Software Foundation, either version 3 of the License, or */
/* (at your option) any later version. */
/* */
/* This program is distributed in the hope that it will be useful, */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
/* GNU General Public License for more details. */
/* */
/* You should have received a copy of the GNU General Public License */
/* along with this program. If not, see <http://www.gnu.org/licenses/>. */
/* */
/*******************************************************************************/
package
com.github.shadowsocks
import
java.util.Locale
import
android.content.SharedPreferences.OnSharedPreferenceChangeListener
import
android.content.
{
Intent
,
SharedPreferences
}
import
android.net.Uri
import
android.os.
{
Build
,
Bundle
,
UserManager
}
import
android.support.design.widget.Snackbar
import
android.support.v14.preference.SwitchPreference
import
android.support.v7.app.AlertDialog
import
android.support.v7.preference.
{
DropDownPreference
,
Preference
}
import
android.webkit.
{
WebView
,
WebViewClient
}
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
be.mygod.preference._
object
ShadowsocksSettings
{
// Constants
private
final
val
TAG
=
"ShadowsocksSettings"
private
val
PROXY_PREFS
=
Array
(
Key
.
name
,
Key
.
host
,
Key
.
remotePort
,
Key
.
localPort
,
Key
.
password
,
Key
.
method
,
Key
.
auth
,
Key
.
kcp
,
Key
.
kcpPort
,
Key
.
kcpcli
)
private
val
FEATURE_PREFS
=
Array
(
Key
.
route
,
Key
.
remoteDns
,
Key
.
proxyApps
,
Key
.
udpdns
,
Key
.
ipv6
)
// Helper functions
def
updateDropDownPreference
(
pref
:
Preference
,
value
:
String
)
{
pref
.
asInstanceOf
[
DropDownPreference
].
setValue
(
value
)
}
def
updateEditTextPreference
(
pref
:
Preference
,
value
:
String
)
{
pref
.
asInstanceOf
[
EditTextPreference
].
setText
(
value
)
}
def
updateNumberPickerPreference
(
pref
:
Preference
,
value
:
Int
)
{
pref
.
asInstanceOf
[
NumberPickerPreference
].
setValue
(
value
)
}
def
updateSwitchPreference
(
pref
:
Preference
,
value
:
Boolean
)
{
pref
.
asInstanceOf
[
SwitchPreference
].
setChecked
(
value
)
}
def
updatePreference
(
pref
:
Preference
,
name
:
String
,
profile
:
Profile
,
demo
:
Boolean
=
false
)
{
name
match
{
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
)
pref
.
setSummary
(
if
(
demo
)
"\u2022"
*
32
else
"%s"
)
case
Key
.
remoteDns
=>
updateEditTextPreference
(
pref
,
profile
.
remoteDns
)
if
(
profile
.
remoteDns
!=
null
)
pref
.
setSummary
(
if
(
demo
)
"8.8.8.8"
else
"%s"
)
case
Key
.
method
=>
updateDropDownPreference
(
pref
,
profile
.
method
)
case
Key
.
route
=>
updateDropDownPreference
(
pref
,
profile
.
route
)
case
Key
.
proxyApps
=>
updateSwitchPreference
(
pref
,
profile
.
proxyApps
)
case
Key
.
udpdns
=>
updateSwitchPreference
(
pref
,
profile
.
udpdns
)
case
Key
.
auth
=>
updateSwitchPreference
(
pref
,
profile
.
auth
)
case
Key
.
ipv6
=>
updateSwitchPreference
(
pref
,
profile
.
ipv6
)
case
Key
.
kcp
=>
updateSwitchPreference
(
pref
,
profile
.
kcp
)
case
Key
.
kcpPort
=>
updateNumberPickerPreference
(
pref
,
profile
.
kcpPort
)
case
Key
.
kcpcli
=>
updateEditTextPreference
(
pref
,
profile
.
kcpcli
)
}
}
}
class
ShadowsocksSettings
extends
PreferenceFragment
with
OnSharedPreferenceChangeListener
{
import
ShadowsocksSettings._
private
def
activity
=
getActivity
.
asInstanceOf
[
Shadowsocks
]
lazy
val
natSwitch
:
SwitchPreference
=
findPreference
(
Key
.
isNAT
).
asInstanceOf
[
SwitchPreference
]
private
var
isProxyApps
:
SwitchPreference
=
_
override
def
onCreatePreferences
(
bundle
:
Bundle
,
key
:
String
)
{
addPreferencesFromResource
(
R
.
xml
.
pref_all
)
getPreferenceManager
.
getSharedPreferences
.
registerOnSharedPreferenceChangeListener
(
this
)
findPreference
(
Key
.
name
).
setOnPreferenceChangeListener
((
_
,
value
)
=>
{
profile
.
name
=
value
.
asInstanceOf
[
String
]
app
.
profileManager
.
updateProfile
(
profile
)
})
findPreference
(
Key
.
host
).
setOnPreferenceChangeListener
((
_
,
value
)
=>
{
profile
.
host
=
value
.
asInstanceOf
[
String
]
app
.
profileManager
.
updateProfile
(
profile
)
})
findPreference
(
Key
.
remotePort
).
setOnPreferenceChangeListener
((
_
,
value
)
=>
{
profile
.
remotePort
=
value
.
asInstanceOf
[
Int
]
app
.
profileManager
.
updateProfile
(
profile
)
})
findPreference
(
Key
.
localPort
).
setOnPreferenceChangeListener
((
_
,
value
)
=>
{
profile
.
localPort
=
value
.
asInstanceOf
[
Int
]
app
.
profileManager
.
updateProfile
(
profile
)
})
findPreference
(
Key
.
password
).
setOnPreferenceChangeListener
((
_
,
value
)
=>
{
profile
.
password
=
value
.
asInstanceOf
[
String
]
app
.
profileManager
.
updateProfile
(
profile
)
})
findPreference
(
Key
.
method
).
setOnPreferenceChangeListener
((
_
,
value
)
=>
{
profile
.
method
=
value
.
asInstanceOf
[
String
]
app
.
profileManager
.
updateProfile
(
profile
)
})
findPreference
(
Key
.
route
).
setOnPreferenceChangeListener
((
_
,
value
)
=>
{
profile
.
route
=
value
.
asInstanceOf
[
String
]
app
.
profileManager
.
updateProfile
(
profile
)
})
findPreference
(
Key
.
remoteDns
).
setOnPreferenceChangeListener
((
_
,
value
)
=>
{
profile
.
remoteDns
=
value
.
asInstanceOf
[
String
]
app
.
profileManager
.
updateProfile
(
profile
)
})
findPreference
(
Key
.
kcp
).
setOnPreferenceChangeListener
((
_
,
value
)
=>
{
profile
.
kcp
=
value
.
asInstanceOf
[
Boolean
]
app
.
profileManager
.
updateProfile
(
profile
)
})
findPreference
(
Key
.
kcpPort
).
setOnPreferenceChangeListener
((
_
,
value
)
=>
{
profile
.
kcpPort
=
value
.
asInstanceOf
[
Int
]
app
.
profileManager
.
updateProfile
(
profile
)
})
findPreference
(
Key
.
kcpcli
).
setOnPreferenceChangeListener
((
_
,
value
)
=>
{
profile
.
kcpcli
=
value
.
asInstanceOf
[
String
]
app
.
profileManager
.
updateProfile
(
profile
)
})
isProxyApps
=
findPreference
(
Key
.
proxyApps
).
asInstanceOf
[
SwitchPreference
]
isProxyApps
.
setOnPreferenceClickListener
(
_
=>
{
startActivity
(
new
Intent
(
activity
,
classOf
[
AppManager
]))
isProxyApps
.
setChecked
(
true
)
false
})
isProxyApps
.
setOnPreferenceChangeListener
((
_
,
value
)
=>
{
profile
.
proxyApps
=
value
.
asInstanceOf
[
Boolean
]
app
.
profileManager
.
updateProfile
(
profile
)
})
findPreference
(
Key
.
udpdns
).
setOnPreferenceChangeListener
((
_
,
value
)
=>
{
profile
.
udpdns
=
value
.
asInstanceOf
[
Boolean
]
app
.
profileManager
.
updateProfile
(
profile
)
})
findPreference
(
Key
.
auth
).
setOnPreferenceChangeListener
((
_
,
value
)
=>
{
profile
.
auth
=
value
.
asInstanceOf
[
Boolean
]
app
.
profileManager
.
updateProfile
(
profile
)
})
findPreference
(
Key
.
ipv6
).
setOnPreferenceChangeListener
((
_
,
value
)
=>
{
profile
.
ipv6
=
value
.
asInstanceOf
[
Boolean
]
app
.
profileManager
.
updateProfile
(
profile
)
})
val
switch
=
findPreference
(
Key
.
isAutoConnect
).
asInstanceOf
[
SwitchPreference
]
switch
.
setOnPreferenceChangeListener
((
_
,
value
)
=>
{
BootReceiver
.
setEnabled
(
activity
,
value
.
asInstanceOf
[
Boolean
])
true
})
if
(
getPreferenceManager
.
getSharedPreferences
.
getBoolean
(
Key
.
isAutoConnect
,
false
))
{
BootReceiver
.
setEnabled
(
activity
,
true
)
getPreferenceManager
.
getSharedPreferences
.
edit
.
remove
(
Key
.
isAutoConnect
).
apply
()
}
switch
.
setChecked
(
BootReceiver
.
getEnabled
(
activity
))
val
tfo
=
findPreference
(
Key
.
tfo
).
asInstanceOf
[
SwitchPreference
]
tfo
.
setChecked
(
TcpFastOpen
.
sendEnabled
)
tfo
.
setOnPreferenceChangeListener
((
_
,
v
)
=>
{
val
value
=
v
.
asInstanceOf
[
Boolean
]
val
result
=
TcpFastOpen
.
enabled
(
value
)
if
(
result
!=
null
&&
result
!=
"Success."
)
Snackbar
.
make
(
activity
.
findViewById
(
android
.
R
.
id
.
content
),
result
,
Snackbar
.
LENGTH_LONG
).
show
()
value
==
TcpFastOpen
.
sendEnabled
})
if
(!
TcpFastOpen
.
supported
)
{
tfo
.
setEnabled
(
false
)
tfo
.
setSummary
(
getString
(
R
.
string
.
tcp_fastopen_summary_unsupported
,
java
.
lang
.
System
.
getProperty
(
"os.version"
)))
}
findPreference
(
"recovery"
).
setOnPreferenceClickListener
(
_
=>
{
app
.
track
(
TAG
,
"reset"
)
activity
.
recovery
()
true
})
findPreference
(
"about"
).
setOnPreferenceClickListener
(
_
=>
{
app
.
track
(
TAG
,
"about"
)
val
web
=
new
WebView
(
activity
)
web
.
loadUrl
(
"file:///android_asset/pages/about.html"
)
web
.
setWebViewClient
(
new
WebViewClient
()
{
override
def
shouldOverrideUrlLoading
(
view
:
WebView
,
url
:
String
)
:
Boolean
=
{
try
{
startActivity
(
new
Intent
(
Intent
.
ACTION_VIEW
,
Uri
.
parse
(
url
)))
}
catch
{
case
_:
android.content.ActivityNotFoundException
=>
// Ignore
}
true
}
})
new
AlertDialog
.
Builder
(
activity
)
.
setTitle
(
getString
(
R
.
string
.
about_title
).
formatLocal
(
Locale
.
ENGLISH
,
BuildConfig
.
VERSION_NAME
))
.
setNegativeButton
(
getString
(
android
.
R
.
string
.
ok
),
null
)
.
setView
(
web
)
.
create
()
.
show
()
true
})
}
override
def
onDisplayPreferenceDialog
(
preference
:
Preference
)
:
Unit
=
preference
.
getKey
match
{
case
Key
.
kcpcli
=>
displayPreferenceDialog
(
Key
.
kcpcli
,
new
KcpCliPreferenceDialogFragment
())
case
_
=>
super
.
onDisplayPreferenceDialog
(
preference
)
}
def
refreshProfile
()
{
profile
=
app
.
currentProfile
match
{
case
Some
(
p
)
=>
p
case
None
=>
app
.
profileManager
.
getFirstProfile
match
{
case
Some
(
p
)
=>
app
.
profileId
(
p
.
id
)
p
case
None
=>
val
default
=
app
.
profileManager
.
createDefault
()
app
.
profileId
(
default
.
id
)
default
}
}
isProxyApps
.
setChecked
(
profile
.
proxyApps
)
}
override
def
onDestroy
()
{
super
.
onDestroy
()
app
.
settings
.
unregisterOnSharedPreferenceChangeListener
(
this
)
}
def
onSharedPreferenceChanged
(
pref
:
SharedPreferences
,
key
:
String
)
:
Unit
=
key
match
{
case
Key
.
isNAT
=>
activity
.
handler
.
post
(()
=>
{
activity
.
detachService
()
activity
.
attachServiceCallback
()
})
case
_
=>
}
private
var
enabled
=
true
def
setEnabled
(
enabled
:
Boolean
)
{
this
.
enabled
=
enabled
for
(
name
<-
Key
.
isNAT
#::
PROXY_PREFS
.
toStream
#:::
FEATURE_PREFS
.
toStream
)
{
val
pref
=
findPreference
(
name
)
if
(
pref
!=
null
)
pref
.
setEnabled
(
enabled
&&
(
name
!=
Key
.
proxyApps
||
Utils
.
isLollipopOrAbove
||
app
.
isNatEnabled
))
}
}
var
profile
:
Profile
=
_
def
setProfile
(
profile
:
Profile
)
{
this
.
profile
=
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
)
}
}
src/main/scala/com/github/shadowsocks/ToolbarFragment.scala
0 → 100644
View file @
dbb137c5
package
com.github.shadowsocks
import
android.app.Fragment
import
android.os.Bundle
import
android.support.v7.widget.Toolbar
import
android.view.View
class
ToolbarFragment
extends
Fragment
{
var
toolbar
:
Toolbar
=
_
override
def
onViewCreated
(
view
:
View
,
savedInstanceState
:
Bundle
)
{
super
.
onViewCreated
(
view
,
savedInstanceState
)
toolbar
=
view
.
findViewById
(
R
.
id
.
toolbar
).
asInstanceOf
[
Toolbar
]
val
activity
=
getActivity
.
asInstanceOf
[
MainActivity
]
activity
.
drawer
.
setToolbar
(
activity
,
toolbar
,
true
)
}
}
src/main/scala/com/github/shadowsocks/database/Profile.scala
View file @
dbb137c5
...
...
@@ -23,8 +23,10 @@ package com.github.shadowsocks.database
import
java.net.URLEncoder
import
java.util.Locale
import
android.content.SharedPreferences
import
android.os.Binder
import
android.util.Base64
import
com.github.shadowsocks.utils.Key
import
com.j256.ormlite.field.
{
DataType
,
DatabaseField
}
class
Profile
{
...
...
@@ -32,7 +34,7 @@ class Profile {
var
id
:
Int
=
0
@DatabaseField
var
name
:
String
=
"
Untitled
"
var
name
:
String
=
""
@DatabaseField
var
host
:
String
=
""
...
...
@@ -100,4 +102,41 @@ class Profile {
'#'
+
URLEncoder
.
encode
(
name
,
"utf-8"
)
def
isMethodUnsafe
:
Boolean
=
"table"
.
equalsIgnoreCase
(
method
)
||
"rc4"
.
equalsIgnoreCase
(
method
)
def
serialize
(
editor
:
SharedPreferences.Editor
)
:
SharedPreferences.Editor
=
editor
.
putString
(
Key
.
name
,
name
)
.
putString
(
Key
.
host
,
host
)
.
putInt
(
Key
.
localPort
,
localPort
)
.
putInt
(
Key
.
remotePort
,
remotePort
)
.
putString
(
Key
.
password
,
password
)
.
putString
(
Key
.
route
,
route
)
.
putString
(
Key
.
remoteDns
,
remoteDns
)
.
putBoolean
(
Key
.
proxyApps
,
proxyApps
)
.
putBoolean
(
Key
.
bypass
,
bypass
)
.
putBoolean
(
Key
.
udpdns
,
udpdns
)
.
putBoolean
(
Key
.
auth
,
auth
)
.
putBoolean
(
Key
.
ipv6
,
ipv6
)
.
putString
(
Key
.
individual
,
individual
)
.
putBoolean
(
Key
.
kcp
,
kcp
)
.
putInt
(
Key
.
kcpPort
,
kcpPort
)
.
putString
(
Key
.
kcpcli
,
kcpcli
)
def
deserialize
(
pref
:
SharedPreferences
)
{
// It's assumed that default values are never used, so 0/false/null is always used even if that isn't the case
name
=
pref
.
getString
(
Key
.
name
,
null
)
host
=
pref
.
getString
(
Key
.
host
,
null
)
localPort
=
pref
.
getInt
(
Key
.
localPort
,
0
)
remotePort
=
pref
.
getInt
(
Key
.
remotePort
,
0
)
password
=
pref
.
getString
(
Key
.
password
,
null
)
route
=
pref
.
getString
(
Key
.
route
,
null
)
remoteDns
=
pref
.
getString
(
Key
.
remoteDns
,
null
)
proxyApps
=
pref
.
getBoolean
(
Key
.
proxyApps
,
false
)
bypass
=
pref
.
getBoolean
(
Key
.
bypass
,
false
)
udpdns
=
pref
.
getBoolean
(
Key
.
udpdns
,
false
)
auth
=
pref
.
getBoolean
(
Key
.
auth
,
false
)
ipv6
=
pref
.
getBoolean
(
Key
.
ipv6
,
false
)
individual
=
pref
.
getString
(
Key
.
individual
,
null
)
kcp
=
pref
.
getBoolean
(
Key
.
kcp
,
false
)
kcpPort
=
pref
.
getInt
(
Key
.
kcpPort
,
0
)
kcpcli
=
pref
.
getString
(
Key
.
kcpcli
,
null
)
}
}
src/main/scala/com/github/shadowsocks/utils/Constants.scala
View file @
dbb137c5
...
...
@@ -151,8 +151,6 @@ object Key {
val
localPort
=
"localPortNum"
val
remoteDns
=
"remoteDns"
val
profileTip
=
"profileTip"
val
kcp
=
"kcp"
val
kcpPort
=
"kcpPort"
val
kcpcli
=
"kcpcli"
...
...
@@ -170,9 +168,14 @@ object State {
}
object
Action
{
val
SERVICE
=
"com.github.shadowsocks.SERVICE"
val
CLOSE
=
"com.github.shadowsocks.CLOSE"
val
QUICK_SWITCH
=
"com.github.shadowsocks.QUICK_SWITCH"
final
val
SERVICE
=
"com.github.shadowsocks.SERVICE"
final
val
CLOSE
=
"com.github.shadowsocks.CLOSE"
final
val
CONNECTED
=
"com.github.shadowsocks.CONNECTED"
// TODO
final
val
PROFILE_CHANGED
=
"com.github.shadowsocks.PROFILE_CHANGED"
final
val
PROFILE_REMOVED
=
"com.github.shadowsocks.PROFILE_REMOVED"
final
val
QUICK_SWITCH
=
"com.github.shadowsocks.QUICK_SWITCH"
final
val
EXTRA_PROFILE_ID
=
"com.github.shadowsocks.EXTRA_PROFILE_ID"
}
object
Route
{
...
...
src/main/scala/com/github/shadowsocks/utils/Typefaces.scala
0 → 100644
View file @
dbb137c5
package
com.github.shadowsocks.utils
import
java.util
import
android.content.Context
import
android.graphics.Typeface
import
android.util.Log
import
com.github.shadowsocks.ShadowsocksApplication.app
object
Typefaces
{
def
get
(
c
:
Context
,
assetPath
:
String
)
:
Typeface
=
{
cache
synchronized
{
if
(!
cache
.
containsKey
(
assetPath
))
{
try
{
cache
.
put
(
assetPath
,
Typeface
.
createFromAsset
(
c
.
getAssets
,
assetPath
))
}
catch
{
case
e
:
Exception
=>
Log
.
e
(
"Typefaces"
,
"Could not get typeface '"
+
assetPath
+
"' because "
+
e
.
getMessage
)
app
.
track
(
e
)
return
null
}
}
return
cache
.
get
(
assetPath
)
}
}
private
final
val
cache
=
new
util
.
Hashtable
[
String
,
Typeface
]
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment