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
a38c9920
Commit
a38c9920
authored
Jan 14, 2017
by
Mygod
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add initial support for configuring plugins
parent
1887c6ff
Changes
20
Hide whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
439 additions
and
23 deletions
+439
-23
mobile/src/main/res/values/dimen.xml
mobile/src/main/res/values/dimen.xml
+1
-0
mobile/src/main/res/values/strings.xml
mobile/src/main/res/values/strings.xml
+6
-0
mobile/src/main/res/xml/pref_profile.xml
mobile/src/main/res/xml/pref_profile.xml
+16
-0
mobile/src/main/scala/com/github/shadowsocks/AppManager.scala
...le/src/main/scala/com/github/shadowsocks/AppManager.scala
+5
-13
mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala
.../scala/com/github/shadowsocks/ProfileConfigFragment.scala
+61
-1
mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala
...scala/com/github/shadowsocks/ShadowsocksApplication.scala
+12
-1
mobile/src/main/scala/com/github/shadowsocks/plugin/NativePlugin.scala
...in/scala/com/github/shadowsocks/plugin/NativePlugin.scala
+16
-0
mobile/src/main/scala/com/github/shadowsocks/plugin/NoPlugin.scala
...c/main/scala/com/github/shadowsocks/plugin/NoPlugin.scala
+11
-0
mobile/src/main/scala/com/github/shadowsocks/plugin/Plugin.scala
...src/main/scala/com/github/shadowsocks/plugin/Plugin.scala
+13
-0
mobile/src/main/scala/com/github/shadowsocks/plugin/PluginConfiguration.scala
...a/com/github/shadowsocks/plugin/PluginConfiguration.scala
+43
-0
mobile/src/main/scala/com/github/shadowsocks/plugin/PluginManager.scala
...n/scala/com/github/shadowsocks/plugin/PluginManager.scala
+25
-0
mobile/src/main/scala/com/github/shadowsocks/plugin/ResolvedPlugin.scala
.../scala/com/github/shadowsocks/plugin/ResolvedPlugin.scala
+16
-0
mobile/src/main/scala/com/github/shadowsocks/preference/BottomSheetPreferenceDialogFragment.scala
...ocks/preference/BottomSheetPreferenceDialogFragment.scala
+77
-0
mobile/src/main/scala/com/github/shadowsocks/preference/IconListPreference.scala
...om/github/shadowsocks/preference/IconListPreference.scala
+65
-0
mobile/src/main/scala/com/github/shadowsocks/preference/PluginConfigurationDialogFragment.scala
...wsocks/preference/PluginConfigurationDialogFragment.scala
+57
-0
plugin/build.sbt
plugin/build.sbt
+2
-0
plugin/src/main/java/com/github/shadowsocks/plugin/PluginInterface.java
...n/java/com/github/shadowsocks/plugin/PluginInterface.java
+3
-1
plugin/src/main/java/com/github/shadowsocks/plugin/PluginOptions.java
...ain/java/com/github/shadowsocks/plugin/PluginOptions.java
+7
-5
plugin/src/main/scala/be/mygod/preference/EditTextPreferenceDialogFragment.scala
...e/mygod/preference/EditTextPreferenceDialogFragment.scala
+1
-1
plugin/src/main/scala/be/mygod/preference/PreferenceFragment.scala
...c/main/scala/be/mygod/preference/PreferenceFragment.scala
+2
-1
No files found.
mobile/src/main/res/values/dimen.xml
View file @
a38c9920
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<resources>
<dimen
name=
"profile_item_max_width"
>
480dp
</dimen>
<dimen
name=
"profile_item_max_width"
>
480dp
</dimen>
<dimen
name=
"icon_list_item_padding"
>
8dp
</dimen>
<!-- TODO: Remove when this issue is fixed: https://github.com/JorgeCastilloPrz/FABProgressCircle/issues/19 -->
<!-- TODO: Remove when this issue is fixed: https://github.com/JorgeCastilloPrz/FABProgressCircle/issues/19 -->
<dimen
name=
"fab_margin_top"
>
-44dp
</dimen>
<dimen
name=
"fab_margin_top"
>
-44dp
</dimen>
</resources>
</resources>
mobile/src/main/res/values/strings.xml
View file @
a38c9920
...
@@ -142,4 +142,10 @@
...
@@ -142,4 +142,10 @@
<string
name=
"acl_rule_templates_generic"
>
Subnet/Hostname PCRE pattern
</string>
<string
name=
"acl_rule_templates_generic"
>
Subnet/Hostname PCRE pattern
</string>
<string
name=
"acl_rule_templates_domain"
>
Domain name and all its subdomain names
</string>
<string
name=
"acl_rule_templates_domain"
>
Domain name and all its subdomain names
</string>
<string
name=
"edit_rule"
>
Edit rule
</string>
<string
name=
"edit_rule"
>
Edit rule
</string>
<!-- plugin -->
<string
name=
"plugin"
>
Plugin
</string>
<string
name=
"plugin_configure"
>
Configure…
</string>
<string
name=
"plugin_disabled"
>
Disabled
</string>
<string
name=
"plugin_unknown"
>
Unknown plugin %s
</string>
</resources>
</resources>
mobile/src/main/res/xml/pref_profile.xml
View file @
a38c9920
...
@@ -71,4 +71,20 @@
...
@@ -71,4 +71,20 @@
</be.mygod.preference.PreferenceCategory>
</be.mygod.preference.PreferenceCategory>
<be.mygod.preference.PreferenceCategory
android:title=
"@string/plugin"
>
<com.github.shadowsocks.preference.IconListPreference
android:key=
"plugin"
android:persistent=
"false"
android:summary=
"%s"
android:title=
"@string/plugin"
/>
<be.mygod.preference.EditTextPreference
android:key=
"plugin.configure"
android:persistent=
"false"
android:summary=
"%s"
android:title=
"@string/plugin_configure"
/>
</be.mygod.preference.PreferenceCategory>
</PreferenceScreen>
</PreferenceScreen>
mobile/src/main/scala/com/github/shadowsocks/AppManager.scala
View file @
a38c9920
...
@@ -48,21 +48,13 @@ object AppManager {
...
@@ -48,21 +48,13 @@ object AppManager {
private
var
instance
:
AppManager
=
_
private
var
instance
:
AppManager
=
_
private
var
receiver
Registered
:
Boolean
=
_
private
var
receiver
:
BroadcastReceiver
=
_
private
var
cachedApps
:
Array
[
ProxiedApp
]
=
_
private
var
cachedApps
:
Array
[
ProxiedApp
]
=
_
private
def
getApps
(
pm
:
PackageManager
)
=
{
private
def
getApps
(
pm
:
PackageManager
)
=
{
if
(!
receiverRegistered
)
{
if
(
receiver
==
null
)
receiver
=
app
.
listenForPackageChanges
{
val
filter
=
new
IntentFilter
(
Intent
.
ACTION_PACKAGE_ADDED
)
synchronized
(
cachedApps
=
null
)
filter
.
addAction
(
Intent
.
ACTION_PACKAGE_REMOVED
)
val
instance
=
AppManager
.
instance
filter
.
addDataScheme
(
"package"
)
if
(
instance
!=
null
)
instance
.
reloadApps
()
app
.
registerReceiver
((
_:
Context
,
intent
:
Intent
)
=>
if
(
intent
.
getAction
!=
Intent
.
ACTION_PACKAGE_REMOVED
||
!
intent
.
getBooleanExtra
(
Intent
.
EXTRA_REPLACING
,
false
))
{
synchronized
(
cachedApps
=
null
)
val
instance
=
AppManager
.
instance
if
(
instance
!=
null
)
instance
.
reloadApps
()
},
filter
)
receiverRegistered
=
true
}
}
synchronized
{
synchronized
{
if
(
cachedApps
==
null
)
cachedApps
=
pm
.
getInstalledPackages
(
PackageManager
.
GET_PERMISSIONS
)
if
(
cachedApps
==
null
)
cachedApps
=
pm
.
getInstalledPackages
(
PackageManager
.
GET_PERMISSIONS
)
...
...
mobile/src/main/scala/com/github/shadowsocks/ProfileConfigFragment.scala
View file @
a38c9920
...
@@ -23,19 +23,33 @@ package com.github.shadowsocks
...
@@ -23,19 +23,33 @@ package com.github.shadowsocks
import
android.content.SharedPreferences.OnSharedPreferenceChangeListener
import
android.content.SharedPreferences.OnSharedPreferenceChangeListener
import
android.content.
{
DialogInterface
,
Intent
,
SharedPreferences
}
import
android.content.
{
DialogInterface
,
Intent
,
SharedPreferences
}
import
android.os.
{
Build
,
Bundle
,
UserManager
}
import
android.os.
{
Build
,
Bundle
,
UserManager
}
import
android.support.design.widget.Snackbar
import
android.support.v14.preference.SwitchPreference
import
android.support.v14.preference.SwitchPreference
import
android.support.v7.app.AlertDialog
import
android.support.v7.app.AlertDialog
import
android.support.v7.preference.Preference
import
android.support.v7.widget.Toolbar.OnMenuItemClickListener
import
android.support.v7.widget.Toolbar.OnMenuItemClickListener
import
android.text.TextUtils
import
android.view.MenuItem
import
android.view.MenuItem
import
be.mygod.preference.
PreferenceFragment
import
be.mygod.preference.
{
EditTextPreference
,
PreferenceFragment
}
import
com.github.shadowsocks.ShadowsocksApplication.app
import
com.github.shadowsocks.ShadowsocksApplication.app
import
com.github.shadowsocks.database.Profile
import
com.github.shadowsocks.database.Profile
import
com.github.shadowsocks.plugin.
{
PluginConfiguration
,
PluginInterface
,
PluginManager
,
PluginOptions
}
import
com.github.shadowsocks.preference.
{
IconListPreference
,
PluginConfigurationDialogFragment
}
import
com.github.shadowsocks.utils.
{
Action
,
Key
,
Utils
}
import
com.github.shadowsocks.utils.
{
Action
,
Key
,
Utils
}
object
ProfileConfigFragment
{
private
final
val
REQUEST_CODE_CONFIGURE
=
1
}
class
ProfileConfigFragment
extends
PreferenceFragment
with
OnMenuItemClickListener
class
ProfileConfigFragment
extends
PreferenceFragment
with
OnMenuItemClickListener
with
OnSharedPreferenceChangeListener
{
with
OnSharedPreferenceChangeListener
{
import
ProfileConfigFragment._
private
var
profile
:
Profile
=
_
private
var
profile
:
Profile
=
_
private
var
isProxyApps
:
SwitchPreference
=
_
private
var
isProxyApps
:
SwitchPreference
=
_
private
var
plugin
:
IconListPreference
=
_
private
var
pluginConfigure
:
EditTextPreference
=
_
private
var
pluginConfiguration
:
PluginConfiguration
=
_
override
def
onCreatePreferences
(
bundle
:
Bundle
,
key
:
String
)
{
override
def
onCreatePreferences
(
bundle
:
Bundle
,
key
:
String
)
{
app
.
profileManager
.
getProfile
(
getActivity
.
getIntent
.
getIntExtra
(
Action
.
EXTRA_PROFILE_ID
,
-
1
))
match
{
app
.
profileManager
.
getProfile
(
getActivity
.
getIntent
.
getIntExtra
(
Action
.
EXTRA_PROFILE_ID
,
-
1
))
match
{
...
@@ -57,9 +71,43 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe
...
@@ -57,9 +71,43 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe
isProxyApps
.
setChecked
(
true
)
isProxyApps
.
setChecked
(
true
)
false
false
})
})
plugin
=
findPreference
(
Key
.
plugin
).
asInstanceOf
[
IconListPreference
]
pluginConfigure
=
findPreference
(
"plugin.configure"
).
asInstanceOf
[
EditTextPreference
]
plugin
.
unknownValueSummary
=
getString
(
R
.
string
.
plugin_unknown
)
plugin
.
setOnPreferenceChangeListener
((
_
,
value
)
=>
{
val
selected
=
value
.
asInstanceOf
[
String
]
pluginConfiguration
=
new
PluginConfiguration
(
pluginConfiguration
.
pluginsOptions
,
selected
)
app
.
editor
.
putString
(
Key
.
plugin
,
pluginConfiguration
.
toString
).
putBoolean
(
Key
.
dirty
,
true
).
apply
()
pluginConfigure
.
setEnabled
(!
TextUtils
.
isEmpty
(
selected
))
true
})
pluginConfigure
.
setOnPreferenceChangeListener
((
_
,
value
)
=>
try
{
val
selected
=
pluginConfiguration
.
selected
pluginConfiguration
=
new
PluginConfiguration
(
pluginConfiguration
.
pluginsOptions
+
(
pluginConfiguration
.
selected
->
new
PluginOptions
(
selected
,
value
.
asInstanceOf
[
String
])),
selected
)
app
.
editor
.
putString
(
Key
.
plugin
,
pluginConfiguration
.
toString
).
putBoolean
(
Key
.
dirty
,
true
).
apply
()
true
}
catch
{
case
exc
:
IllegalArgumentException
=>
Snackbar
.
make
(
getActivity
.
findViewById
(
R
.
id
.
snackbar
),
exc
.
getLocalizedMessage
,
Snackbar
.
LENGTH_LONG
).
show
()
false
})
initPlugins
()
app
.
listenForPackageChanges
(
getView
.
post
(
initPlugins
))
app
.
settings
.
registerOnSharedPreferenceChangeListener
(
this
)
app
.
settings
.
registerOnSharedPreferenceChangeListener
(
this
)
}
}
def
initPlugins
()
{
val
plugins
=
PluginManager
.
fetchPlugins
()
plugin
.
setEntries
(
plugins
.
map
(
_
.
label
))
plugin
.
setEntryValues
(
plugins
.
map
(
_
.
id
.
asInstanceOf
[
CharSequence
]))
plugin
.
setEntryIcons
(
plugins
.
map
(
_
.
icon
))
pluginConfiguration
=
new
PluginConfiguration
(
app
.
settings
.
getString
(
Key
.
plugin
,
null
))
plugin
.
setValue
(
pluginConfiguration
.
selected
)
plugin
.
checkSummary
()
pluginConfigure
.
setText
(
pluginConfiguration
.
selectedOptions
.
toString
)
}
override
def
onSharedPreferenceChanged
(
sharedPreferences
:
SharedPreferences
,
key
:
String
)
:
Unit
=
override
def
onSharedPreferenceChanged
(
sharedPreferences
:
SharedPreferences
,
key
:
String
)
:
Unit
=
if
(
key
!=
Key
.
proxyApps
&&
findPreference
(
key
)
!=
null
)
app
.
editor
.
putBoolean
(
Key
.
dirty
,
true
).
apply
()
if
(
key
!=
Key
.
proxyApps
&&
findPreference
(
key
)
!=
null
)
app
.
editor
.
putBoolean
(
Key
.
dirty
,
true
).
apply
()
...
@@ -73,6 +121,18 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe
...
@@ -73,6 +121,18 @@ class ProfileConfigFragment extends PreferenceFragment with OnMenuItemClickListe
isProxyApps
.
setChecked
(
app
.
settings
.
getBoolean
(
Key
.
proxyApps
,
false
))
// fetch proxyApps updated by AppManager
isProxyApps
.
setChecked
(
app
.
settings
.
getBoolean
(
Key
.
proxyApps
,
false
))
// fetch proxyApps updated by AppManager
}
}
override
def
onDisplayPreferenceDialog
(
preference
:
Preference
)
:
Unit
=
if
(
preference
eq
pluginConfigure
)
{
val
selected
=
pluginConfiguration
.
selected
val
intent
=
new
Intent
(
PluginInterface
.
ACTION_CONFIGURE
(
selected
))
if
(
intent
.
resolveActivity
(
getActivity
.
getPackageManager
)
!=
null
)
startActivityForResult
(
intent
.
putExtra
(
PluginInterface
.
EXTRA_OPTIONS
,
pluginConfiguration
.
selectedOptions
.
toString
),
REQUEST_CODE_CONFIGURE
)
else
{
val
bundle
=
new
Bundle
()
bundle
.
putString
(
PluginConfigurationDialogFragment
.
PLUGIN_ID_FRAGMENT_TAG
,
selected
)
displayPreferenceDialog
(
preference
.
getKey
,
new
PluginConfigurationDialogFragment
,
bundle
)
}
}
else
super
.
onDisplayPreferenceDialog
(
preference
)
override
def
onMenuItemClick
(
item
:
MenuItem
)
:
Boolean
=
item
.
getItemId
match
{
override
def
onMenuItemClick
(
item
:
MenuItem
)
:
Boolean
=
item
.
getItemId
match
{
case
R
.
id
.
action_delete
=>
case
R
.
id
.
action_delete
=>
new
AlertDialog
.
Builder
(
getActivity
)
new
AlertDialog
.
Builder
(
getActivity
)
...
...
mobile/src/main/scala/com/github/shadowsocks/ShadowsocksApplication.scala
View file @
a38c9920
...
@@ -27,7 +27,7 @@ import java.util.concurrent.TimeUnit
...
@@ -27,7 +27,7 @@ import java.util.concurrent.TimeUnit
import
android.annotation.SuppressLint
import
android.annotation.SuppressLint
import
android.app.Application
import
android.app.Application
import
android.content.
SharedPreferences
import
android.content.
_
import
android.content.res.Configuration
import
android.content.res.Configuration
import
android.os.
{
Build
,
LocaleList
}
import
android.os.
{
Build
,
LocaleList
}
import
android.preference.PreferenceManager
import
android.preference.PreferenceManager
...
@@ -210,4 +210,15 @@ class ShadowsocksApplication extends Application {
...
@@ -210,4 +210,15 @@ class ShadowsocksApplication extends Application {
}
}
def
updateAssets
()
:
Unit
=
if
(
settings
.
getInt
(
Key
.
currentVersionCode
,
-
1
)
!=
BuildConfig
.
VERSION_CODE
)
copyAssets
()
def
updateAssets
()
:
Unit
=
if
(
settings
.
getInt
(
Key
.
currentVersionCode
,
-
1
)
!=
BuildConfig
.
VERSION_CODE
)
copyAssets
()
def
listenForPackageChanges
(
callback
:
=>
Unit
)
:
BroadcastReceiver
=
{
val
filter
=
new
IntentFilter
(
Intent
.
ACTION_PACKAGE_ADDED
)
filter
.
addAction
(
Intent
.
ACTION_PACKAGE_REMOVED
)
filter
.
addDataScheme
(
"package"
)
val
result
:
BroadcastReceiver
=
(
_:
Context
,
intent
:
Intent
)
=>
if
(
intent
.
getAction
!=
Intent
.
ACTION_PACKAGE_REMOVED
||
!
intent
.
getBooleanExtra
(
Intent
.
EXTRA_REPLACING
,
false
))
callback
app
.
registerReceiver
(
result
,
filter
)
result
}
}
}
mobile/src/main/scala/com/github/shadowsocks/plugin/NativePlugin.scala
0 → 100644
View file @
a38c9920
package
com.github.shadowsocks.plugin
import
android.content.pm.
{
PackageManager
,
ResolveInfo
}
/**
* @author Mygod
*/
class
NativePlugin
(
resolveInfo
:
ResolveInfo
,
packageManager
:
PackageManager
)
extends
ResolvedPlugin
(
resolveInfo
,
packageManager
)
{
assert
(
resolveInfo
.
providerInfo
!=
null
)
assert
(
resolveInfo
.
providerInfo
.
authority
.
startsWith
(
PluginInterface
.
AUTHORITY_BASE
))
override
final
lazy
val
id
:
String
=
resolveInfo
.
providerInfo
.
authority
.
substring
(
PluginInterface
.
AUTHORITY_BASE
.
length
)
override
final
lazy
val
defaultConfig
:
String
=
resolveInfo
.
providerInfo
.
metaData
.
getString
(
PluginInterface
.
METADATA_KEY_DEFAULT_CONFIG
)
}
mobile/src/main/scala/com/github/shadowsocks/plugin/NoPlugin.scala
0 → 100644
View file @
a38c9920
package
com.github.shadowsocks.plugin
import
com.github.shadowsocks.ShadowsocksApplication.app
/**
* @author Mygod
*/
object
NoPlugin
extends
Plugin
{
override
def
id
:
String
=
""
override
def
label
:
CharSequence
=
app
.
getText
(
com
.
github
.
shadowsocks
.
R
.
string
.
plugin_disabled
)
}
mobile/src/main/scala/com/github/shadowsocks/plugin/Plugin.scala
0 → 100644
View file @
a38c9920
package
com.github.shadowsocks.plugin
import
android.graphics.drawable.Drawable
/**
* @author Mygod
*/
abstract
class
Plugin
{
def
id
:
String
def
label
:
CharSequence
def
icon
:
Drawable
=
null
def
defaultConfig
:
String
=
null
}
mobile/src/main/scala/com/github/shadowsocks/plugin/PluginConfiguration.scala
0 → 100644
View file @
a38c9920
package
com.github.shadowsocks.plugin
import
android.util.Log
import
com.github.shadowsocks.utils.Commandline
import
scala.collection.mutable
/**
* @author Mygod
*/
class
PluginConfiguration
(
val
pluginsOptions
:
Map
[
String
,
PluginOptions
],
val
selected
:
String
)
{
private
def
this
(
plugins
:
Array
[
PluginOptions
])
=
this
(
plugins
.
map
(
opt
=>
opt
.
id
->
opt
).
toMap
,
if
(
plugins
.
isEmpty
)
""
else
plugins
(
0
).
id
)
def
this
(
plugin
:
String
)
=
this
(
if
(
plugin
==
null
)
Array
[
PluginOptions
]()
else
plugin
.
split
(
"\n"
).
map
{
case
line
if
line
.
startsWith
(
"kcptun "
)
=>
val
opt
=
new
PluginOptions
()
opt
.
id
=
"kcptun"
try
{
val
args
=
mutable
.
Queue
(
Commandline
.
translateCommandline
(
line
)
:
_
*
)
args
.
dequeue
()
while
(
args
.
nonEmpty
)
args
.
dequeue
()
match
{
case
"--nocomp"
=>
opt
.
put
(
"nocomp"
,
"1"
)
case
option
if
option
.
startsWith
(
"--"
)
=>
opt
.
put
(
option
.
substring
(
2
),
args
.
dequeue
())
case
option
=>
throw
new
IllegalArgumentException
(
"Unknown kcptun parameter: "
+
option
)
}
}
catch
{
case
exc
:
Exception
=>
Log
.
w
(
"PluginConfiguration"
,
exc
.
getMessage
)
}
opt
case
line
=>
new
PluginOptions
(
line
)
})
def
selectedOptions
:
PluginOptions
=
pluginsOptions
(
selected
)
override
def
toString
:
String
=
{
val
result
=
new
mutable
.
ListBuffer
[
PluginOptions
]()
for
((
id
,
opt
)
<-
pluginsOptions
)
id
match
{
case
this
.
selected
=>
result
.
prepend
(
opt
)
case
_
=>
result
.
append
(
opt
)
}
result
.
map
(
_
.
toString
(
false
)).
mkString
(
"\n"
)
}
}
mobile/src/main/scala/com/github/shadowsocks/plugin/PluginManager.scala
0 → 100644
View file @
a38c9920
package
com.github.shadowsocks.plugin
import
android.content.pm.PackageManager
import
android.content.
{
BroadcastReceiver
,
Intent
}
import
com.github.shadowsocks.ShadowsocksApplication.app
import
scala.collection.JavaConverters._
/**
* @author Mygod
*/
object
PluginManager
{
private
var
receiver
:
BroadcastReceiver
=
_
private
var
cachedPlugins
:
Array
[
Plugin
]
=
_
def
fetchPlugins
()
:
Array
[
Plugin
]
=
{
if
(
receiver
==
null
)
receiver
=
app
.
listenForPackageChanges
(
synchronized
(
cachedPlugins
=
null
))
synchronized
(
if
(
cachedPlugins
==
null
)
{
val
pm
=
app
.
getPackageManager
cachedPlugins
=
(
NoPlugin
+:
pm
.
queryIntentContentProviders
(
new
Intent
().
addCategory
(
PluginInterface
.
CATEGORY_NATIVE_PLUGIN
),
PackageManager
.
GET_META_DATA
).
asScala
.
map
(
new
NativePlugin
(
_
,
pm
))).
toArray
})
cachedPlugins
}
}
mobile/src/main/scala/com/github/shadowsocks/plugin/ResolvedPlugin.scala
0 → 100644
View file @
a38c9920
package
com.github.shadowsocks.plugin
import
android.content.pm.
{
PackageManager
,
ResolveInfo
}
import
android.graphics.drawable.Drawable
import
com.github.shadowsocks.ShadowsocksApplication.app
/**
* Base class for any kind of plugin that can be used.
*
* @author Mygod
*/
abstract
class
ResolvedPlugin
(
resolveInfo
:
ResolveInfo
,
packageManager
:
PackageManager
=
app
.
getPackageManager
)
extends
Plugin
{
override
final
lazy
val
label
:
CharSequence
=
resolveInfo
.
loadLabel
(
packageManager
)
override
final
lazy
val
icon
:
Drawable
=
resolveInfo
.
loadIcon
(
packageManager
)
}
mobile/src/main/scala/com/github/shadowsocks/preference/BottomSheetPreferenceDialogFragment.scala
0 → 100644
View file @
a38c9920
package
com.github.shadowsocks.preference
import
android.graphics.Typeface
import
android.graphics.drawable.Drawable
import
android.os.Bundle
import
android.support.design.widget.BottomSheetDialog
import
android.support.v14.preference.PreferenceDialogFragment
import
android.support.v7.widget.RecyclerView.ViewHolder
import
android.support.v7.widget.
{
LinearLayoutManager
,
RecyclerView
}
import
android.view.ViewGroup.LayoutParams
import
android.view.
{
LayoutInflater
,
View
,
ViewGroup
}
import
android.widget.TextView
import
com.github.shadowsocks.R
/**
* @author Mygod
*/
final
class
BottomSheetPreferenceDialogFragment
extends
PreferenceDialogFragment
{
private
lazy
val
preference
=
getPreference
.
asInstanceOf
[
IconListPreference
]
private
lazy
val
index
:
Int
=
preference
.
selectedEntry
private
lazy
val
entries
:
Array
[
CharSequence
]
=
preference
.
getEntries
private
lazy
val
entryIcons
:
Array
[
Drawable
]
=
preference
.
getEntryIcons
private
var
clickedIndex
=
-
1
override
def
onCreateDialog
(
savedInstanceState
:
Bundle
)
:
BottomSheetDialog
=
{
val
activity
=
getActivity
val
dialog
=
new
BottomSheetDialog
(
activity
,
getTheme
)
val
recycler
=
new
RecyclerView
(
activity
)
recycler
.
setHasFixedSize
(
true
)
recycler
.
setLayoutManager
(
new
LinearLayoutManager
(
activity
))
recycler
.
setAdapter
(
new
IconListAdapter
(
dialog
))
recycler
.
setLayoutParams
(
new
LayoutParams
(
LayoutParams
.
MATCH_PARENT
,
LayoutParams
.
WRAP_CONTENT
))
dialog
.
setContentView
(
recycler
)
dialog
}
def
onDialogClosed
(
positiveResult
:
Boolean
)
:
Unit
=
if
(
clickedIndex
>=
0
)
{
val
value
=
preference
.
getEntryValues
()(
clickedIndex
).
toString
if
(
preference
.
callChangeListener
(
value
))
preference
.
setValue
(
value
)
}
private
final
class
IconListViewHolder
(
val
dialog
:
BottomSheetDialog
,
val
view
:
TextView
)
extends
ViewHolder
(
view
)
with
View
.
OnClickListener
{
private
var
index
:
Int
=
_
{
val
padding
=
getResources
.
getDimension
(
R
.
dimen
.
icon_list_item_padding
).
toInt
view
.
setPadding
(
padding
,
padding
,
padding
,
padding
)
view
.
setCompoundDrawablePadding
(
padding
)
view
.
setOnClickListener
(
this
)
val
typedArray
=
dialog
.
getContext
.
obtainStyledAttributes
(
Array
(
android
.
R
.
attr
.
selectableItemBackground
))
view
.
setBackgroundResource
(
typedArray
.
getResourceId
(
0
,
0
))
typedArray
.
recycle
()
}
def
bind
(
i
:
Int
,
selected
:
Boolean
=
false
)
{
view
.
setText
(
entries
(
i
))
view
.
setCompoundDrawablesWithIntrinsicBounds
(
entryIcons
(
i
),
null
,
null
,
null
)
view
.
setTypeface
(
null
,
if
(
selected
)
Typeface
.
BOLD
else
Typeface
.
NORMAL
)
index
=
i
}
def
onClick
(
v
:
View
)
{
clickedIndex
=
index
dialog
.
dismiss
()
}
}
private
final
class
IconListAdapter
(
dialog
:
BottomSheetDialog
)
extends
RecyclerView
.
Adapter
[
IconListViewHolder
]
{
def
getItemCount
:
Int
=
entries
.
length
def
onBindViewHolder
(
vh
:
IconListViewHolder
,
i
:
Int
)
:
Unit
=
if
(
index
<
0
)
vh
.
bind
(
i
)
else
i
match
{
case
0
=>
vh
.
bind
(
index
,
selected
=
true
)
case
_
if
i
>
index
=>
vh
.
bind
(
i
)
case
_
=>
vh
.
bind
(
i
-
1
)
}
def
onCreateViewHolder
(
vg
:
ViewGroup
,
i
:
Int
)
:
IconListViewHolder
=
new
IconListViewHolder
(
dialog
,
LayoutInflater
.
from
(
vg
.
getContext
).
inflate
(
android
.
R
.
layout
.
simple_list_item_1
,
vg
,
false
).
asInstanceOf
[
TextView
])
}
}
mobile/src/main/scala/com/github/shadowsocks/preference/IconListPreference.scala
0 → 100644
View file @
a38c9920
package
com.github.shadowsocks.preference
import
android.content.Context
import
android.graphics.drawable.Drawable
import
android.support.annotation.ArrayRes
import
android.support.v7.preference.ListPreference
import
android.support.v7.preference.Preference.OnPreferenceChangeListener
import
android.util.AttributeSet
import
be.mygod.preference.DialogPreferencePlus
/**
* @author Mygod
*/
class
IconListPreference
(
context
:
Context
,
attrs
:
AttributeSet
=
null
)
extends
ListPreference
(
context
,
attrs
)
with
DialogPreferencePlus
{
override
def
createDialog
()
=
new
BottomSheetPreferenceDialogFragment
()
private
var
mEntryIcons
:
Array
[
Drawable
]
=
_
def
selectedEntry
:
Int
=
getEntryValues
.
indexOf
(
getValue
)
var
unknownValueSummary
:
String
=
_
private
var
listener
:
OnPreferenceChangeListener
=
_
override
def
getOnPreferenceChangeListener
:
OnPreferenceChangeListener
=
listener
override
def
setOnPreferenceChangeListener
(
listener
:
OnPreferenceChangeListener
)
:
Unit
=
this
.
listener
=
listener
super
.
setOnPreferenceChangeListener
((
preference
,
newValue
)
=>
if
(
listener
==
null
||
listener
.
onPreferenceChange
(
preference
,
newValue
))
{
setValue
(
newValue
.
toString
)
checkSummary
()
if
(
mEntryIcons
!=
null
)
setIcon
(
getEntryIcon
)
true
}
else
false
)
def
checkSummary
()
:
Unit
=
if
(
unknownValueSummary
!=
null
)
setSummary
(
if
(
selectedEntry
<
0
)
unknownValueSummary
.
format
(
getValue
)
else
"%s"
)
// {
// val a: TypedArray = context.obtainStyledAttributes(attrs, R.styleable.IconListPreference)
// val entryIconsResId: Int = a.getResourceId(R.styleable.IconListPreference_entryIcons, -1)
// if (entryIconsResId != -1) setEntryIcons(entryIconsResId)
// a.recycle()
// }
def
getEntryIcon
:
Drawable
=
mEntryIcons
(
selectedEntry
)
def
getEntryIcons
:
Array
[
Drawable
]
=
mEntryIcons
def
setEntryIcons
(
entryIcons
:
Array
[
Drawable
])
:
Unit
=
mEntryIcons
=
entryIcons
def
setEntryIcons
(
@ArrayRes
entryIconsResId
:
Int
)
{
val
icons_array
=
getContext
.
getResources
.
obtainTypedArray
(
entryIconsResId
)
val
icon_ids_array
=
new
Array
[
Drawable
](
icons_array
.
length
)
for
(
i
<-
0
until
icons_array
.
length
)
icon_ids_array
(
i
)
=
icons_array
.
getDrawable
(
i
)
setEntryIcons
(
icon_ids_array
)
icons_array
.
recycle
()
}
def
init
()
{
val
entryValues
=
getEntryValues
if
(
entryValues
==
null
)
return
if
(
mEntryIcons
!=
null
)
setIcon
(
getEntryIcon
)
}
protected
override
def
onSetInitialValue
(
restoreValue
:
Boolean
,
defaultValue
:
AnyRef
)
{
super
.
onSetInitialValue
(
restoreValue
,
defaultValue
)
init
()
}
}
mobile/src/main/scala/com/github/shadowsocks/preference/PluginConfigurationDialogFragment.scala
0 → 100644
View file @
a38c9920
/*******************************************************************************/
/* */
/* Copyright (C) 2017 by Max Lv <max.c.lv@gmail.com> */
/* Copyright (C) 2017 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.preference
import
android.app.
{
Activity
,
AlertDialog
}
import
android.content.
{
DialogInterface
,
Intent
}
import
be.mygod.preference.EditTextPreferenceDialogFragment
import
com.github.shadowsocks.plugin.PluginInterface
/**
* @author Mygod
*/
object
PluginConfigurationDialogFragment
{
final
val
PLUGIN_ID_FRAGMENT_TAG
=
"com.github.shadowsocks.preference.PluginConfigurationDialogFragment.PLUGIN_ID"
private
final
val
REQUEST_CODE_HELP
=
1
}
class
PluginConfigurationDialogFragment
extends
EditTextPreferenceDialogFragment
{
import
PluginConfigurationDialogFragment._
override
def
onPrepareDialogBuilder
(
builder
:
AlertDialog.Builder
)
{
super
.
onPrepareDialogBuilder
(
builder
)
val
intent
=
new
Intent
(
PluginInterface
.
ACTION_HELP
(
getArguments
.
getString
(
PLUGIN_ID_FRAGMENT_TAG
)))
if
(
intent
.
resolveActivity
(
getContext
.
getPackageManager
)
!=
null
)
builder
.
setNeutralButton
(
"?"
,
((
_
,
_
)
=>
startActivityForResult
(
intent
.
putExtra
(
PluginInterface
.
EXTRA_OPTIONS
,
editText
.
getText
.
toString
),
REQUEST_CODE_HELP
))
:
DialogInterface.OnClickListener
)
}
override
def
onActivityResult
(
requestCode
:
Int
,
resultCode
:
Int
,
data
:
Intent
)
:
Unit
=
requestCode
match
{
case
REQUEST_CODE_HELP
=>
requestCode
match
{
case
Activity
.
RESULT_OK
=>
new
AlertDialog
.
Builder
(
getContext
)
.
setTitle
(
"?"
)
.
setMessage
(
data
.
getCharSequenceExtra
(
PluginInterface
.
EXTRA_HELP_MESSAGE
))
.
show
()
case
_
=>
}
case
_
=>
super
.
onActivityResult
(
requestCode
,
resultCode
,
data
)
}
}
plugin/build.sbt
View file @
a38c9920
...
@@ -3,3 +3,5 @@ android.useSupportVectors
...
@@ -3,3 +3,5 @@ android.useSupportVectors
name
:=
"plugin"
name
:=
"plugin"
version
:=
"0.0.1-SNAPSHOT"
version
:=
"0.0.1-SNAPSHOT"
publishTo
:=
Some
(
Resolver
.
file
(
"file"
,
new
File
(
Path
.
userHome
.
absolutePath
+
"/.m2/repository"
)))
plugin/src/main/java/com/github/shadowsocks/plugin/PluginInterface.java
View file @
a38c9920
...
@@ -51,6 +51,8 @@ public final class PluginInterface {
...
@@ -51,6 +51,8 @@ public final class PluginInterface {
*/
*/
public
static
final
String
COLUMN_PATH
=
"path"
;
public
static
final
String
COLUMN_PATH
=
"path"
;
static
final
String
AUTHORITY_BASE
=
"com.github.shadowsocks.plugin."
;
/**
/**
* Authority to use for native plugin ContentProvider.
* Authority to use for native plugin ContentProvider.
*
*
...
@@ -58,7 +60,7 @@ public final class PluginInterface {
...
@@ -58,7 +60,7 @@ public final class PluginInterface {
* @return com.github.shadowsocks.plugin.$PLUGIN_ID
* @return com.github.shadowsocks.plugin.$PLUGIN_ID
*/
*/
public
static
String
getAuthority
(
String
pluginId
)
{
public
static
String
getAuthority
(
String
pluginId
)
{
return
"com.github.shadowsocks.plugin."
+
pluginId
;
return
AUTHORITY_BASE
+
pluginId
;
}
}
/**
/**
...
...
plugin/src/main/java/com/github/shadowsocks/plugin/PluginOptions.java
View file @
a38c9920
...
@@ -37,8 +37,10 @@ public final class PluginOptions extends HashMap<String, String> {
...
@@ -37,8 +37,10 @@ public final class PluginOptions extends HashMap<String, String> {
key
=
current
.
toString
();
key
=
current
.
toString
();
current
.
setLength
(
0
);
current
.
setLength
(
0
);
}
else
if
(
";"
.
equals
(
nextToken
))
{
}
else
if
(
";"
.
equals
(
nextToken
))
{
if
(
current
.
length
()
>
0
)
put
(
key
,
current
.
toString
());
if
(
key
!=
null
)
{
else
if
(
firstEntry
)
id
=
key
;
put
(
key
,
current
.
toString
());
key
=
null
;
}
else
if
(
firstEntry
)
id
=
current
.
toString
();
else
throw
new
IllegalArgumentException
(
"Value missing in "
+
options
);
else
throw
new
IllegalArgumentException
(
"Value missing in "
+
options
);
firstEntry
=
false
;
firstEntry
=
false
;
}
}
...
@@ -49,7 +51,7 @@ public final class PluginOptions extends HashMap<String, String> {
...
@@ -49,7 +51,7 @@ public final class PluginOptions extends HashMap<String, String> {
this
.
id
=
id
;
this
.
id
=
id
;
}
}
public
String
id
;
public
String
id
=
""
;
private
static
void
append
(
StringBuilder
result
,
String
str
)
{
private
static
void
append
(
StringBuilder
result
,
String
str
)
{
for
(
int
i
=
0
;
i
<
str
.
length
();
++
i
)
{
for
(
int
i
=
0
;
i
<
str
.
length
();
++
i
)
{
...
@@ -62,7 +64,7 @@ public final class PluginOptions extends HashMap<String, String> {
...
@@ -62,7 +64,7 @@ public final class PluginOptions extends HashMap<String, String> {
}
}
public
String
toString
(
boolean
trimId
)
{
public
String
toString
(
boolean
trimId
)
{
final
StringBuilder
result
=
new
StringBuilder
();
final
StringBuilder
result
=
new
StringBuilder
();
if
(!
trimId
&&
!
TextUtils
.
isEmpty
(
id
))
append
(
result
,
id
);
if
(!
trimId
)
if
(
TextUtils
.
isEmpty
(
id
))
return
""
;
else
append
(
result
,
id
);
for
(
Entry
<
String
,
String
>
entry
:
entrySet
())
if
(
entry
.
getValue
()
!=
null
)
{
for
(
Entry
<
String
,
String
>
entry
:
entrySet
())
if
(
entry
.
getValue
()
!=
null
)
{
if
(
result
.
length
()
>
0
)
result
.
append
(
';'
);
if
(
result
.
length
()
>
0
)
result
.
append
(
';'
);
append
(
result
,
entry
.
getKey
());
append
(
result
,
entry
.
getKey
());
...
@@ -73,6 +75,6 @@ public final class PluginOptions extends HashMap<String, String> {
...
@@ -73,6 +75,6 @@ public final class PluginOptions extends HashMap<String, String> {
}
}
@Override
@Override
public
String
toString
()
{
public
String
toString
()
{
return
toString
(
fals
e
);
return
toString
(
tru
e
);
}
}
}
}
plugin/src/main/scala/be/mygod/preference/EditTextPreferenceDialogFragment.scala
View file @
a38c9920
...
@@ -27,7 +27,7 @@ import android.view.{View, ViewGroup}
...
@@ -27,7 +27,7 @@ import android.view.{View, ViewGroup}
class
EditTextPreferenceDialogFragment
extends
PreferenceDialogFragment
{
class
EditTextPreferenceDialogFragment
extends
PreferenceDialogFragment
{
private
lazy
val
preference
=
getPreference
.
asInstanceOf
[
EditTextPreference
]
private
lazy
val
preference
=
getPreference
.
asInstanceOf
[
EditTextPreference
]
pr
ivate
lazy
val
e
ditText
=
preference
.
editText
pr
otected
lazy
val
editText
:
AppCompatE
ditText
=
preference
.
editText
override
protected
def
onCreateDialogView
(
context
:
Context
)
:
AppCompatEditText
=
{
override
protected
def
onCreateDialogView
(
context
:
Context
)
:
AppCompatEditText
=
{
val
parent
=
editText
.
getParent
.
asInstanceOf
[
ViewGroup
]
val
parent
=
editText
.
getParent
.
asInstanceOf
[
ViewGroup
]
...
...
plugin/src/main/scala/be/mygod/preference/PreferenceFragment.scala
View file @
a38c9920
...
@@ -30,9 +30,10 @@ abstract class PreferenceFragment extends Base {
...
@@ -30,9 +30,10 @@ abstract class PreferenceFragment extends Base {
override
def
onCreateView
(
inflater
:
LayoutInflater
,
container
:
ViewGroup
,
savedInstanceState
:
Bundle
)
:
View
=
override
def
onCreateView
(
inflater
:
LayoutInflater
,
container
:
ViewGroup
,
savedInstanceState
:
Bundle
)
:
View
=
super
.
onCreateView
(
inflater
,
container
,
savedInstanceState
)
super
.
onCreateView
(
inflater
,
container
,
savedInstanceState
)
protected
final
def
displayPreferenceDialog
(
key
:
String
,
fragment
:
DialogFragment
)
{
protected
final
def
displayPreferenceDialog
(
key
:
String
,
fragment
:
DialogFragment
,
other
:
Bundle
=
null
)
{
val
bundle
=
new
Bundle
(
1
)
val
bundle
=
new
Bundle
(
1
)
bundle
.
putString
(
"key"
,
key
)
bundle
.
putString
(
"key"
,
key
)
if
(
other
!=
null
)
bundle
.
putAll
(
other
)
fragment
.
setArguments
(
bundle
)
fragment
.
setArguments
(
bundle
)
fragment
.
setTargetFragment
(
this
,
0
)
fragment
.
setTargetFragment
(
this
,
0
)
getFragmentManager
.
beginTransaction
()
getFragmentManager
.
beginTransaction
()
...
...
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