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
0055e53a
Unverified
Commit
0055e53a
authored
Jul 30, 2018
by
Max Lv
Committed by
GitHub
Jul 30, 2018
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1892 from Mygod/json
Add import/export to JSON
parents
31fdcfaf
ca386a75
Changes
18
Hide whitespace changes
Inline
Side-by-side
Showing
18 changed files
with
283 additions
and
77 deletions
+283
-77
mobile/src/androidTest/java/com/github/shadowsocks/database/ProfileTest.kt
...dTest/java/com/github/shadowsocks/database/ProfileTest.kt
+1
-1
mobile/src/main/java/com/github/shadowsocks/AppManager.kt
mobile/src/main/java/com/github/shadowsocks/AppManager.kt
+2
-2
mobile/src/main/java/com/github/shadowsocks/MainActivity.kt
mobile/src/main/java/com/github/shadowsocks/MainActivity.kt
+3
-2
mobile/src/main/java/com/github/shadowsocks/ProfilesFragment.kt
.../src/main/java/com/github/shadowsocks/ProfilesFragment.kt
+63
-5
mobile/src/main/java/com/github/shadowsocks/ScannerActivity.kt
...e/src/main/java/com/github/shadowsocks/ScannerActivity.kt
+19
-16
mobile/src/main/java/com/github/shadowsocks/acl/CustomRulesFragment.kt
...in/java/com/github/shadowsocks/acl/CustomRulesFragment.kt
+1
-1
mobile/src/main/java/com/github/shadowsocks/bg/BaseService.kt
...le/src/main/java/com/github/shadowsocks/bg/BaseService.kt
+9
-14
mobile/src/main/java/com/github/shadowsocks/database/Profile.kt
.../src/main/java/com/github/shadowsocks/database/Profile.kt
+92
-1
mobile/src/main/java/com/github/shadowsocks/database/ProfileManager.kt
...in/java/com/github/shadowsocks/database/ProfileManager.kt
+1
-13
mobile/src/main/java/com/github/shadowsocks/utils/ArrayIterator.kt
...c/main/java/com/github/shadowsocks/utils/ArrayIterator.kt
+51
-0
mobile/src/main/java/com/github/shadowsocks/utils/Utils.kt
mobile/src/main/java/com/github/shadowsocks/utils/Utils.kt
+1
-10
mobile/src/main/res/drawable/ic_file_file_upload.xml
mobile/src/main/res/drawable/ic_file_file_upload.xml
+9
-0
mobile/src/main/res/menu/app_manager_menu.xml
mobile/src/main/res/menu/app_manager_menu.xml
+2
-2
mobile/src/main/res/menu/custom_rules_menu.xml
mobile/src/main/res/menu/custom_rules_menu.xml
+1
-1
mobile/src/main/res/menu/profile_manager_menu.xml
mobile/src/main/res/menu/profile_manager_menu.xml
+24
-7
mobile/src/main/res/menu/profile_share_popup.xml
mobile/src/main/res/menu/profile_share_popup.xml
+1
-1
mobile/src/main/res/menu/scanner_menu.xml
mobile/src/main/res/menu/scanner_menu.xml
+1
-1
mobile/src/main/res/values/strings.xml
mobile/src/main/res/values/strings.xml
+2
-0
No files found.
mobile/src/androidTest/java/com/github/shadowsocks/database/ProfileTest.kt
View file @
0055e53a
...
@@ -27,7 +27,7 @@ import org.junit.Test
...
@@ -27,7 +27,7 @@ import org.junit.Test
class
ProfileTest
{
class
ProfileTest
{
@Test
@Test
fun
parsing
()
{
fun
parsing
()
{
val
results
=
Profile
.
findAll
(
"garble ss://YmYtY2ZiOnRlc3RAMTkyLjE2OC4xMDAuMTo4ODg4#example-server garble"
)
val
results
=
Profile
.
findAll
Urls
(
"garble ss://YmYtY2ZiOnRlc3RAMTkyLjE2OC4xMDAuMTo4ODg4#example-server garble"
)
.
toList
()
.
toList
()
Assert
.
assertEquals
(
1
,
results
.
size
)
Assert
.
assertEquals
(
1
,
results
.
size
)
Assert
.
assertEquals
(
"ss://YmYtY2ZiOnRlc3Q@192.168.100.1:8888#example-server"
.
toUri
(),
Assert
.
assertEquals
(
"ss://YmYtY2ZiOnRlc3Q@192.168.100.1:8888#example-server"
.
toUri
(),
...
...
mobile/src/main/java/com/github/shadowsocks/AppManager.kt
View file @
0055e53a
...
@@ -223,13 +223,13 @@ class AppManager : AppCompatActivity(), Toolbar.OnMenuItemClickListener {
...
@@ -223,13 +223,13 @@ class AppManager : AppCompatActivity(), Toolbar.OnMenuItemClickListener {
}
else
Snackbar
.
make
(
appListView
,
R
.
string
.
action_export_err
,
Snackbar
.
LENGTH_LONG
).
show
()
}
else
Snackbar
.
make
(
appListView
,
R
.
string
.
action_export_err
,
Snackbar
.
LENGTH_LONG
).
show
()
return
true
return
true
}
}
R
.
id
.
action_export
->
{
R
.
id
.
action_export
_clipboard
->
{
clipboard
.
primaryClip
=
ClipData
.
newPlainText
(
Key
.
individual
,
clipboard
.
primaryClip
=
ClipData
.
newPlainText
(
Key
.
individual
,
"${DataStore.bypass}\n${DataStore.individual}"
)
"${DataStore.bypass}\n${DataStore.individual}"
)
Snackbar
.
make
(
appListView
,
R
.
string
.
action_export_msg
,
Snackbar
.
LENGTH_LONG
).
show
()
Snackbar
.
make
(
appListView
,
R
.
string
.
action_export_msg
,
Snackbar
.
LENGTH_LONG
).
show
()
return
true
return
true
}
}
R
.
id
.
action_import
->
{
R
.
id
.
action_import
_clipboard
->
{
val
proxiedAppString
=
clipboard
.
primaryClip
?.
getItemAt
(
0
)
?.
text
?.
toString
()
val
proxiedAppString
=
clipboard
.
primaryClip
?.
getItemAt
(
0
)
?.
text
?.
toString
()
if
(!
proxiedAppString
.
isNullOrEmpty
())
{
if
(!
proxiedAppString
.
isNullOrEmpty
())
{
val
i
=
proxiedAppString
!!
.
indexOf
(
'\n'
)
val
i
=
proxiedAppString
!!
.
indexOf
(
'\n'
)
...
...
mobile/src/main/java/com/github/shadowsocks/MainActivity.kt
View file @
0055e53a
...
@@ -200,7 +200,8 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, OnPre
...
@@ -200,7 +200,8 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, OnPre
}
}
override
fun
onActivityResult
(
requestCode
:
Int
,
resultCode
:
Int
,
data
:
Intent
?)
{
override
fun
onActivityResult
(
requestCode
:
Int
,
resultCode
:
Int
,
data
:
Intent
?)
{
if
(
resultCode
==
Activity
.
RESULT_OK
)
app
.
startService
()
else
{
if
(
requestCode
!=
REQUEST_CONNECT
)
super
.
onActivityResult
(
requestCode
,
resultCode
,
data
)
else
if
(
resultCode
==
Activity
.
RESULT_OK
)
app
.
startService
()
else
{
snackbar
().
setText
(
R
.
string
.
vpn_permission_denied
).
show
()
snackbar
().
setText
(
R
.
string
.
vpn_permission_denied
).
show
()
Crashlytics
.
log
(
Log
.
ERROR
,
TAG
,
"Failed to start VpnService from onActivityResult: $data"
)
Crashlytics
.
log
(
Log
.
ERROR
,
TAG
,
"Failed to start VpnService from onActivityResult: $data"
)
}
}
...
@@ -272,7 +273,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, OnPre
...
@@ -272,7 +273,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, OnPre
else
->
null
else
->
null
}
}
if
(
sharedStr
.
isNullOrEmpty
())
return
if
(
sharedStr
.
isNullOrEmpty
())
return
val
profiles
=
Profile
.
findAll
(
sharedStr
).
toList
()
val
profiles
=
Profile
.
findAll
Urls
(
sharedStr
,
app
.
currentProfile
).
toList
()
if
(
profiles
.
isEmpty
())
{
if
(
profiles
.
isEmpty
())
{
snackbar
().
setText
(
R
.
string
.
profile_invalid_input
).
show
()
snackbar
().
setText
(
R
.
string
.
profile_invalid_input
).
show
()
return
return
...
...
mobile/src/main/java/com/github/shadowsocks/ProfilesFragment.kt
View file @
0055e53a
...
@@ -21,6 +21,7 @@
...
@@ -21,6 +21,7 @@
package
com.github.shadowsocks
package
com.github.shadowsocks
import
android.annotation.SuppressLint
import
android.annotation.SuppressLint
import
android.app.Activity
import
android.content.ClipData
import
android.content.ClipData
import
android.content.ClipboardManager
import
android.content.ClipboardManager
import
android.content.Context
import
android.content.Context
...
@@ -48,11 +49,14 @@ import com.github.shadowsocks.database.ProfileManager
...
@@ -48,11 +49,14 @@ import com.github.shadowsocks.database.ProfileManager
import
com.github.shadowsocks.plugin.PluginConfiguration
import
com.github.shadowsocks.plugin.PluginConfiguration
import
com.github.shadowsocks.preference.DataStore
import
com.github.shadowsocks.preference.DataStore
import
com.github.shadowsocks.utils.Action
import
com.github.shadowsocks.utils.Action
import
com.github.shadowsocks.utils.datas
import
com.github.shadowsocks.utils.printLog
import
com.github.shadowsocks.widget.UndoSnackbarManager
import
com.github.shadowsocks.widget.UndoSnackbarManager
import
com.google.android.gms.ads.AdRequest
import
com.google.android.gms.ads.AdRequest
import
com.google.android.gms.ads.AdSize
import
com.google.android.gms.ads.AdSize
import
com.google.android.gms.ads.AdView
import
com.google.android.gms.ads.AdView
import
net.glxn.qrgen.android.QRCode
import
net.glxn.qrgen.android.QRCode
import
org.json.JSONArray
class
ProfilesFragment
:
ToolbarFragment
(),
Toolbar
.
OnMenuItemClickListener
{
class
ProfilesFragment
:
ToolbarFragment
(),
Toolbar
.
OnMenuItemClickListener
{
companion
object
{
companion
object
{
...
@@ -62,6 +66,8 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
...
@@ -62,6 +66,8 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
var
instance
:
ProfilesFragment
?
=
null
var
instance
:
ProfilesFragment
?
=
null
private
const
val
KEY_URL
=
"com.github.shadowsocks.QRCodeDialog.KEY_URL"
private
const
val
KEY_URL
=
"com.github.shadowsocks.QRCodeDialog.KEY_URL"
private
const
val
REQUEST_IMPORT_PROFILES
=
1
private
const
val
REQUEST_EXPORT_PROFILES
=
2
}
}
/**
/**
...
@@ -210,7 +216,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
...
@@ -210,7 +216,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
.
commitAllowingStateLoss
()
.
commitAllowingStateLoss
()
true
true
}
}
R
.
id
.
action_export
->
{
R
.
id
.
action_export
_clipboard
->
{
clipboard
.
primaryClip
=
ClipData
.
newPlainText
(
null
,
this
.
item
.
toString
())
clipboard
.
primaryClip
=
ClipData
.
newPlainText
(
null
,
this
.
item
.
toString
())
true
true
}
}
...
@@ -363,9 +369,10 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
...
@@ -363,9 +369,10 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
startActivity
(
Intent
(
context
,
ScannerActivity
::
class
.
java
))
startActivity
(
Intent
(
context
,
ScannerActivity
::
class
.
java
))
true
true
}
}
R
.
id
.
action_import
->
{
R
.
id
.
action_import
_clipboard
->
{
try
{
try
{
val
profiles
=
Profile
.
findAll
(
clipboard
.
primaryClip
!!
.
getItemAt
(
0
).
text
).
toList
()
val
profiles
=
Profile
.
findAllUrls
(
clipboard
.
primaryClip
!!
.
getItemAt
(
0
).
text
,
app
.
currentProfile
)
.
toList
()
if
(
profiles
.
isNotEmpty
())
{
if
(
profiles
.
isNotEmpty
())
{
profiles
.
forEach
{
ProfileManager
.
createProfile
(
it
)
}
profiles
.
forEach
{
ProfileManager
.
createProfile
(
it
)
}
(
activity
as
MainActivity
).
snackbar
().
setText
(
R
.
string
.
action_import_msg
).
show
()
(
activity
as
MainActivity
).
snackbar
().
setText
(
R
.
string
.
action_import_msg
).
show
()
...
@@ -377,11 +384,20 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
...
@@ -377,11 +384,20 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
(
activity
as
MainActivity
).
snackbar
().
setText
(
R
.
string
.
action_import_err
).
show
()
(
activity
as
MainActivity
).
snackbar
().
setText
(
R
.
string
.
action_import_err
).
show
()
true
true
}
}
R
.
id
.
action_import_file
->
{
startActivityForResult
(
Intent
(
Intent
.
ACTION_GET_CONTENT
).
apply
{
addCategory
(
Intent
.
CATEGORY_OPENABLE
)
type
=
"application/json"
putExtra
(
Intent
.
EXTRA_ALLOW_MULTIPLE
,
true
)
},
REQUEST_IMPORT_PROFILES
)
true
}
R
.
id
.
action_manual_settings
->
{
R
.
id
.
action_manual_settings
->
{
startConfig
(
ProfileManager
.
createProfile
())
startConfig
(
ProfileManager
.
createProfile
(
Profile
().
also
{
app
.
currentProfile
?.
copyFeatureSettingsTo
(
it
)
}))
true
true
}
}
R
.
id
.
action_export
->
{
R
.
id
.
action_export
_clipboard
->
{
val
profiles
=
ProfileManager
.
getAllProfiles
()
val
profiles
=
ProfileManager
.
getAllProfiles
()
(
activity
as
MainActivity
).
snackbar
().
setText
(
if
(
profiles
!=
null
)
{
(
activity
as
MainActivity
).
snackbar
().
setText
(
if
(
profiles
!=
null
)
{
clipboard
.
primaryClip
=
ClipData
.
newPlainText
(
null
,
profiles
.
joinToString
(
"\n"
))
clipboard
.
primaryClip
=
ClipData
.
newPlainText
(
null
,
profiles
.
joinToString
(
"\n"
))
...
@@ -389,10 +405,52 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
...
@@ -389,10 +405,52 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
}
else
R
.
string
.
action_export_err
).
show
()
}
else
R
.
string
.
action_export_err
).
show
()
true
true
}
}
R
.
id
.
action_export_file
->
{
startActivityForResult
(
Intent
(
Intent
.
ACTION_CREATE_DOCUMENT
).
apply
{
addCategory
(
Intent
.
CATEGORY_OPENABLE
)
type
=
"application/json"
putExtra
(
Intent
.
EXTRA_TITLE
,
"profiles.json"
)
// optional title that can be edited
},
REQUEST_EXPORT_PROFILES
)
true
}
else
->
false
else
->
false
}
}
}
}
override
fun
onActivityResult
(
requestCode
:
Int
,
resultCode
:
Int
,
data
:
Intent
?)
{
if
(
resultCode
!=
Activity
.
RESULT_OK
)
super
.
onActivityResult
(
requestCode
,
resultCode
,
data
)
else
when
(
requestCode
)
{
REQUEST_IMPORT_PROFILES
->
{
val
feature
=
app
.
currentProfile
var
success
=
false
val
activity
=
activity
as
MainActivity
for
(
uri
in
data
!!
.
datas
)
try
{
Profile
.
parseJson
(
activity
.
contentResolver
.
openInputStream
(
uri
).
bufferedReader
().
readText
(),
feature
).
forEach
{
ProfileManager
.
createProfile
(
it
)
success
=
true
}
}
catch
(
e
:
Exception
)
{
printLog
(
e
)
}
activity
.
snackbar
().
setText
(
if
(
success
)
R
.
string
.
action_import_msg
else
R
.
string
.
action_import_err
)
.
show
()
}
REQUEST_EXPORT_PROFILES
->
{
val
profiles
=
ProfileManager
.
getAllProfiles
()
if
(
profiles
!=
null
)
try
{
requireContext
().
contentResolver
.
openOutputStream
(
data
!!
.
data
).
bufferedWriter
().
use
{
it
.
write
(
JSONArray
(
profiles
.
map
{
it
.
toJson
()
}.
toTypedArray
()).
toString
(
2
))
}
}
catch
(
e
:
Exception
)
{
printLog
(
e
)
(
activity
as
MainActivity
).
snackbar
(
e
.
localizedMessage
).
show
()
}
}
else
->
super
.
onActivityResult
(
requestCode
,
resultCode
,
data
)
}
}
override
fun
onTrafficUpdated
(
profileId
:
Long
,
txRate
:
Long
,
rxRate
:
Long
,
txTotal
:
Long
,
rxTotal
:
Long
)
{
override
fun
onTrafficUpdated
(
profileId
:
Long
,
txRate
:
Long
,
rxRate
:
Long
,
txTotal
:
Long
,
rxTotal
:
Long
)
{
if
(
profileId
!=
-
1L
)
{
// ignore resets from MainActivity
if
(
profileId
!=
-
1L
)
{
// ignore resets from MainActivity
if
(
bandwidthProfile
!=
profileId
)
{
if
(
bandwidthProfile
!=
profileId
)
{
...
...
mobile/src/main/java/com/github/shadowsocks/ScannerActivity.kt
View file @
0055e53a
...
@@ -35,9 +35,12 @@ import androidx.appcompat.app.AppCompatActivity
...
@@ -35,9 +35,12 @@ import androidx.appcompat.app.AppCompatActivity
import
androidx.appcompat.widget.Toolbar
import
androidx.appcompat.widget.Toolbar
import
androidx.core.app.TaskStackBuilder
import
androidx.core.app.TaskStackBuilder
import
androidx.core.content.getSystemService
import
androidx.core.content.getSystemService
import
androidx.core.util.forEach
import
com.crashlytics.android.Crashlytics
import
com.crashlytics.android.Crashlytics
import
com.github.shadowsocks.App.Companion.app
import
com.github.shadowsocks.database.Profile
import
com.github.shadowsocks.database.Profile
import
com.github.shadowsocks.database.ProfileManager
import
com.github.shadowsocks.database.ProfileManager
import
com.github.shadowsocks.utils.datas
import
com.github.shadowsocks.utils.openBitmap
import
com.github.shadowsocks.utils.openBitmap
import
com.github.shadowsocks.utils.printLog
import
com.github.shadowsocks.utils.printLog
import
com.github.shadowsocks.utils.resolveResourceId
import
com.github.shadowsocks.utils.resolveResourceId
...
@@ -107,7 +110,7 @@ class ScannerActivity : AppCompatActivity(), Toolbar.OnMenuItemClickListener, Ba
...
@@ -107,7 +110,7 @@ class ScannerActivity : AppCompatActivity(), Toolbar.OnMenuItemClickListener, Ba
}
}
override
fun
onRetrieved
(
barcode
:
Barcode
)
=
runOnUiThread
{
override
fun
onRetrieved
(
barcode
:
Barcode
)
=
runOnUiThread
{
Profile
.
findAll
(
barcode
.
rawValu
e
).
forEach
{
ProfileManager
.
createProfile
(
it
)
}
Profile
.
findAll
Urls
(
barcode
.
rawValue
,
app
.
currentProfil
e
).
forEach
{
ProfileManager
.
createProfile
(
it
)
}
navigateUp
()
navigateUp
()
}
}
override
fun
onRetrievedMultiple
(
closetToClick
:
Barcode
?,
barcode
:
MutableList
<
BarcodeGraphic
>?)
=
check
(
false
)
override
fun
onRetrievedMultiple
(
closetToClick
:
Barcode
?,
barcode
:
MutableList
<
BarcodeGraphic
>?)
=
check
(
false
)
...
@@ -119,31 +122,31 @@ class ScannerActivity : AppCompatActivity(), Toolbar.OnMenuItemClickListener, Ba
...
@@ -119,31 +122,31 @@ class ScannerActivity : AppCompatActivity(), Toolbar.OnMenuItemClickListener, Ba
}
}
override
fun
onMenuItemClick
(
item
:
MenuItem
)
=
when
(
item
.
itemId
)
{
override
fun
onMenuItemClick
(
item
:
MenuItem
)
=
when
(
item
.
itemId
)
{
R
.
id
.
action_import
->
{
R
.
id
.
action_import
_clipboard
->
{
startImport
(
true
)
startImport
(
true
)
true
true
}
}
else
->
false
else
->
false
}
}
private
fun
startImport
(
manual
:
Boolean
=
false
)
=
startActivityForResult
(
Intent
(
Intent
.
ACTION_OPEN_DOCUMENT
)
private
fun
startImport
(
manual
:
Boolean
=
false
)
=
startActivityForResult
(
Intent
(
Intent
.
ACTION_GET_CONTENT
).
apply
{
.
addCategory
(
Intent
.
CATEGORY_OPENABLE
)
addCategory
(
Intent
.
CATEGORY_OPENABLE
)
.
setType
(
"image/*"
)
type
=
"image/*"
.
putExtra
(
Intent
.
EXTRA_ALLOW_MULTIPLE
,
true
),
if
(
manual
)
REQUEST_IMPORT
else
REQUEST_IMPORT_OR_FINISH
)
putExtra
(
Intent
.
EXTRA_ALLOW_MULTIPLE
,
true
)
},
if
(
manual
)
REQUEST_IMPORT
else
REQUEST_IMPORT_OR_FINISH
)
override
fun
onActivityResult
(
requestCode
:
Int
,
resultCode
:
Int
,
data
:
Intent
?)
{
override
fun
onActivityResult
(
requestCode
:
Int
,
resultCode
:
Int
,
data
:
Intent
?)
{
when
(
requestCode
)
{
when
(
requestCode
)
{
REQUEST_IMPORT
,
REQUEST_IMPORT_OR_FINISH
->
if
(
resultCode
==
Activity
.
RESULT_OK
)
{
REQUEST_IMPORT
,
REQUEST_IMPORT_OR_FINISH
->
if
(
resultCode
==
Activity
.
RESULT_OK
)
{
val
feature
=
app
.
currentProfile
var
success
=
false
var
success
=
false
var
list
=
listOfNotNull
(
data
?.
data
)
for
(
uri
in
data
!!
.
datas
)
try
{
val
clipData
=
data
?.
clipData
detector
.
detect
(
Frame
.
Builder
().
setBitmap
(
contentResolver
.
openBitmap
(
uri
)).
build
())
if
(
clipData
!=
null
)
list
+=
(
0
until
clipData
.
itemCount
).
map
{
clipData
.
getItemAt
(
it
).
uri
}
.
forEach
{
_
,
barcode
->
for
(
uri
in
list
)
try
{
Profile
.
findAllUrls
(
barcode
.
rawValue
,
feature
).
forEach
{
val
barcodes
=
detector
.
detect
(
Frame
.
Builder
()
ProfileManager
.
createProfile
(
it
)
.
setBitmap
(
contentResolver
.
openBitmap
(
uri
)).
build
())
success
=
true
for
(
i
in
0
until
barcodes
.
size
())
Profile
.
findAll
(
barcodes
.
valueAt
(
i
).
rawValue
).
forEach
{
}
ProfileManager
.
createProfile
(
it
)
}
success
=
true
}
}
catch
(
e
:
Exception
)
{
}
catch
(
e
:
Exception
)
{
printLog
(
e
)
printLog
(
e
)
}
}
...
...
mobile/src/main/java/com/github/shadowsocks/acl/CustomRulesFragment.kt
View file @
0055e53a
...
@@ -420,7 +420,7 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener,
...
@@ -420,7 +420,7 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener,
dialog
.
show
()
dialog
.
show
()
true
true
}
}
R
.
id
.
action_import
->
{
R
.
id
.
action_import
_clipboard
->
{
try
{
try
{
check
(
adapter
.
addToProxy
(
clipboard
.
primaryClip
!!
.
getItemAt
(
0
).
text
.
toString
())
!=
null
)
check
(
adapter
.
addToProxy
(
clipboard
.
primaryClip
!!
.
getItemAt
(
0
).
text
.
toString
())
!=
null
)
}
catch
(
exc
:
Exception
)
{
}
catch
(
exc
:
Exception
)
{
...
...
mobile/src/main/java/com/github/shadowsocks/bg/BaseService.kt
View file @
0055e53a
...
@@ -47,6 +47,9 @@ import com.github.shadowsocks.plugin.PluginOptions
...
@@ -47,6 +47,9 @@ import com.github.shadowsocks.plugin.PluginOptions
import
com.github.shadowsocks.preference.DataStore
import
com.github.shadowsocks.preference.DataStore
import
com.github.shadowsocks.utils.*
import
com.github.shadowsocks.utils.*
import
com.google.firebase.analytics.FirebaseAnalytics
import
com.google.firebase.analytics.FirebaseAnalytics
import
okhttp3.FormBody
import
okhttp3.OkHttpClient
import
okhttp3.Request
import
java.io.File
import
java.io.File
import
java.io.IOException
import
java.io.IOException
import
java.net.InetAddress
import
java.net.InetAddress
...
@@ -54,10 +57,6 @@ import java.net.UnknownHostException
...
@@ -54,10 +57,6 @@ import java.net.UnknownHostException
import
java.security.MessageDigest
import
java.security.MessageDigest
import
java.util.*
import
java.util.*
import
java.util.concurrent.TimeUnit
import
java.util.concurrent.TimeUnit
import
okhttp3.FormBody
import
okhttp3.OkHttpClient
import
okhttp3.Request
import
org.json.JSONObject
/**
/**
* This object uses WeakMap to simulate the effects of multi-inheritance.
* This object uses WeakMap to simulate the effects of multi-inheritance.
...
@@ -186,11 +185,7 @@ object BaseService {
...
@@ -186,11 +185,7 @@ object BaseService {
internal
var
shadowsocksConfigFile
:
File
?
=
null
internal
var
shadowsocksConfigFile
:
File
?
=
null
internal
fun
buildShadowsocksConfig
():
File
{
internal
fun
buildShadowsocksConfig
():
File
{
val
profile
=
profile
!!
val
profile
=
profile
!!
val
config
=
JSONObject
()
val
config
=
profile
.
toJson
(
true
)
.
put
(
"server"
,
profile
.
host
)
.
put
(
"server_port"
,
profile
.
remotePort
)
.
put
(
"password"
,
profile
.
password
)
.
put
(
"method"
,
profile
.
method
)
val
pluginPath
=
pluginPath
val
pluginPath
=
pluginPath
if
(
pluginPath
!=
null
)
{
if
(
pluginPath
!=
null
)
{
val
pluginCmd
=
arrayListOf
(
pluginPath
)
val
pluginCmd
=
arrayListOf
(
pluginPath
)
...
@@ -200,12 +195,12 @@ object BaseService {
...
@@ -200,12 +195,12 @@ object BaseService {
.
put
(
"plugin_opts"
,
plugin
.
toString
())
.
put
(
"plugin_opts"
,
plugin
.
toString
())
}
}
// sensitive Shadowsocks config is stored in
// sensitive Shadowsocks config is stored in
val
file
=
File
(
if
(
UserManagerCompat
.
isUserUnlocked
(
app
))
app
.
filesDir
else
@TargetApi
(
24
)
{
return
File
(
if
(
UserManagerCompat
.
isUserUnlocked
(
app
))
app
.
filesDir
else
@TargetApi
(
24
)
{
app
.
deviceStorage
.
noBackupFilesDir
// only API 24+ will be in locked state
app
.
deviceStorage
.
noBackupFilesDir
// only API 24+ will be in locked state
},
CONFIG_FILE
)
},
CONFIG_FILE
)
.
apply
{
shadowsocksConfigFile
=
file
shadowsocksConfigFile
=
this
file
.
writeText
(
config
.
toString
())
writeText
(
config
.
toString
())
return
file
}
}
}
val
aclFile
:
File
?
get
()
{
val
aclFile
:
File
?
get
()
{
...
...
mobile/src/main/java/com/github/shadowsocks/database/Profile.kt
View file @
0055e53a
...
@@ -26,9 +26,14 @@ import android.util.Log
...
@@ -26,9 +26,14 @@ import android.util.Log
import
androidx.core.net.toUri
import
androidx.core.net.toUri
import
androidx.room.*
import
androidx.room.*
import
com.github.shadowsocks.plugin.PluginConfiguration
import
com.github.shadowsocks.plugin.PluginConfiguration
import
com.github.shadowsocks.plugin.PluginOptions
import
com.github.shadowsocks.preference.DataStore
import
com.github.shadowsocks.preference.DataStore
import
com.github.shadowsocks.utils.Key
import
com.github.shadowsocks.utils.Key
import
com.github.shadowsocks.utils.asIterable
import
com.github.shadowsocks.utils.parsePort
import
com.github.shadowsocks.utils.parsePort
import
org.json.JSONArray
import
org.json.JSONObject
import
org.json.JSONTokener
import
java.io.Serializable
import
java.io.Serializable
import
java.net.URI
import
java.net.URI
import
java.net.URISyntaxException
import
java.net.URISyntaxException
...
@@ -43,13 +48,14 @@ class Profile : Serializable {
...
@@ -43,13 +48,14 @@ class Profile : Serializable {
private
val
userInfoPattern
=
"^(.+?):(.*)$"
.
toRegex
()
private
val
userInfoPattern
=
"^(.+?):(.*)$"
.
toRegex
()
private
val
legacyPattern
=
"^(.+?):(.*)@(.+?):(\\d+?)$"
.
toRegex
()
private
val
legacyPattern
=
"^(.+?):(.*)@(.+?):(\\d+?)$"
.
toRegex
()
fun
findAll
(
data
:
CharSequence
?
)
=
pattern
.
findAll
(
data
?:
""
).
map
{
fun
findAll
Urls
(
data
:
CharSequence
?,
feature
:
Profile
?
=
null
)
=
pattern
.
findAll
(
data
?:
""
).
map
{
val
uri
=
it
.
value
.
toUri
()
val
uri
=
it
.
value
.
toUri
()
try
{
try
{
if
(
uri
.
userInfo
==
null
)
{
if
(
uri
.
userInfo
==
null
)
{
val
match
=
legacyPattern
.
matchEntire
(
String
(
Base64
.
decode
(
uri
.
host
,
Base64
.
NO_PADDING
)))
val
match
=
legacyPattern
.
matchEntire
(
String
(
Base64
.
decode
(
uri
.
host
,
Base64
.
NO_PADDING
)))
if
(
match
!=
null
)
{
if
(
match
!=
null
)
{
val
profile
=
Profile
()
val
profile
=
Profile
()
feature
?.
copyFeatureSettingsTo
(
profile
)
profile
.
method
=
match
.
groupValues
[
1
].
toLowerCase
()
profile
.
method
=
match
.
groupValues
[
1
].
toLowerCase
()
profile
.
password
=
match
.
groupValues
[
2
]
profile
.
password
=
match
.
groupValues
[
2
]
profile
.
host
=
match
.
groupValues
[
3
]
profile
.
host
=
match
.
groupValues
[
3
]
...
@@ -66,6 +72,7 @@ class Profile : Serializable {
...
@@ -66,6 +72,7 @@ class Profile : Serializable {
Base64
.
NO_PADDING
or
Base64
.
NO_WRAP
or
Base64
.
URL_SAFE
)))
Base64
.
NO_PADDING
or
Base64
.
NO_WRAP
or
Base64
.
URL_SAFE
)))
if
(
match
!=
null
)
{
if
(
match
!=
null
)
{
val
profile
=
Profile
()
val
profile
=
Profile
()
feature
?.
copyFeatureSettingsTo
(
profile
)
profile
.
method
=
match
.
groupValues
[
1
]
profile
.
method
=
match
.
groupValues
[
1
]
profile
.
password
=
match
.
groupValues
[
2
]
profile
.
password
=
match
.
groupValues
[
2
]
// bug in Android: https://code.google.com/p/android/issues/detail?id=192855
// bug in Android: https://code.google.com/p/android/issues/detail?id=192855
...
@@ -92,6 +99,54 @@ class Profile : Serializable {
...
@@ -92,6 +99,54 @@ class Profile : Serializable {
null
null
}
}
}.
filterNotNull
()
}.
filterNotNull
()
private
class
JsonParser
(
private
val
feature
:
Profile
?
=
null
)
:
ArrayList
<
Profile
>()
{
private
fun
tryAdd
(
json
:
JSONObject
)
{
val
host
=
json
.
optString
(
"server"
)
if
(
host
.
isNullOrEmpty
())
return
val
remotePort
=
json
.
optInt
(
"server_port"
)
if
(
remotePort
<=
0
)
return
val
password
=
json
.
optString
(
"password"
)
if
(
password
.
isNullOrEmpty
())
return
val
method
=
json
.
optString
(
"method"
)
if
(
method
.
isNullOrEmpty
())
return
add
(
Profile
().
also
{
it
.
host
=
host
it
.
remotePort
=
remotePort
it
.
password
=
password
it
.
method
=
method
}.
apply
{
feature
?.
copyFeatureSettingsTo
(
this
)
val
id
=
json
.
optString
(
"plugin"
)
if
(!
id
.
isNullOrEmpty
())
{
plugin
=
PluginOptions
(
id
,
json
.
optString
(
"plugin_opts"
)).
toString
(
false
)
}
name
=
json
.
optString
(
"remarks"
)
route
=
json
.
optString
(
"route"
,
route
)
remoteDns
=
json
.
optString
(
"remote_dns"
,
remoteDns
)
ipv6
=
json
.
optBoolean
(
"ipv6"
,
ipv6
)
json
.
optJSONObject
(
"proxy_apps"
)
?.
also
{
proxyApps
=
it
.
optBoolean
(
"enabled"
,
proxyApps
)
bypass
=
it
.
optBoolean
(
"bypass"
,
bypass
)
individual
=
it
.
optJSONArray
(
"android_list"
)
?.
asIterable
()
?.
joinToString
(
"\n"
)
?:
individual
}
udpdns
=
json
.
optBoolean
(
"udpdns"
,
udpdns
)
})
}
fun
process
(
json
:
Any
)
{
when
(
json
)
{
is
JSONObject
->
{
tryAdd
(
json
)
for
(
key
in
json
.
keys
())
process
(
json
.
get
(
key
))
}
is
JSONArray
->
json
.
asIterable
().
forEach
(
this
::
process
)
// ignore other types
}
}
}
fun
parseJson
(
json
:
String
,
feature
:
Profile
?
=
null
):
List
<
Profile
>
=
JsonParser
(
feature
).
apply
{
process
(
JSONTokener
(
json
).
nextValue
())
}
}
}
@
androidx
.
room
.
Dao
@
androidx
.
room
.
Dao
...
@@ -143,6 +198,15 @@ class Profile : Serializable {
...
@@ -143,6 +198,15 @@ class Profile : Serializable {
val
formattedAddress
get
()
=
(
if
(
host
.
contains
(
":"
))
"[%s]:%d"
else
"%s:%d"
).
format
(
host
,
remotePort
)
val
formattedAddress
get
()
=
(
if
(
host
.
contains
(
":"
))
"[%s]:%d"
else
"%s:%d"
).
format
(
host
,
remotePort
)
val
formattedName
get
()
=
if
(
name
.
isNullOrEmpty
())
formattedAddress
else
name
!!
val
formattedName
get
()
=
if
(
name
.
isNullOrEmpty
())
formattedAddress
else
name
!!
fun
copyFeatureSettingsTo
(
profile
:
Profile
)
{
profile
.
route
=
route
profile
.
ipv6
=
ipv6
profile
.
proxyApps
=
proxyApps
profile
.
bypass
=
bypass
profile
.
individual
=
individual
profile
.
udpdns
=
udpdns
}
fun
toUri
():
Uri
{
fun
toUri
():
Uri
{
val
builder
=
Uri
.
Builder
()
val
builder
=
Uri
.
Builder
()
.
scheme
(
"ss"
)
.
scheme
(
"ss"
)
...
@@ -158,6 +222,33 @@ class Profile : Serializable {
...
@@ -158,6 +222,33 @@ class Profile : Serializable {
}
}
override
fun
toString
()
=
toUri
().
toString
()
override
fun
toString
()
=
toUri
().
toString
()
fun
toJson
(
compat
:
Boolean
=
false
)
=
JSONObject
().
apply
{
put
(
"server"
,
host
)
put
(
"server_port"
,
remotePort
)
put
(
"password"
,
password
)
put
(
"method"
,
method
)
if
(
compat
)
return
@apply
PluginConfiguration
(
plugin
?:
""
).
selectedOptions
.
apply
{
if
(
id
.
isNotEmpty
())
{
put
(
"plugin"
,
id
)
put
(
"plugin_opts"
,
toString
())
}
}
put
(
"remarks"
,
name
)
put
(
"route"
,
route
)
put
(
"remote_dns"
,
remoteDns
)
put
(
"ipv6"
,
ipv6
)
put
(
"proxy_apps"
,
JSONObject
().
apply
{
put
(
"enabled"
,
proxyApps
)
if
(
proxyApps
)
{
put
(
"bypass"
,
bypass
)
// android_ prefix is used because package names are Android specific
put
(
"android_list"
,
JSONArray
(
individual
.
split
(
"\n"
)))
}
})
put
(
"udpdns"
,
udpdns
)
}
fun
serialize
()
{
fun
serialize
()
{
DataStore
.
privateStore
.
putString
(
Key
.
name
,
name
)
DataStore
.
privateStore
.
putString
(
Key
.
name
,
name
)
DataStore
.
privateStore
.
putString
(
Key
.
host
,
host
)
DataStore
.
privateStore
.
putString
(
Key
.
host
,
host
)
...
...
mobile/src/main/java/com/github/shadowsocks/database/ProfileManager.kt
View file @
0055e53a
...
@@ -21,7 +21,6 @@
...
@@ -21,7 +21,6 @@
package
com.github.shadowsocks.database
package
com.github.shadowsocks.database
import
android.database.sqlite.SQLiteCantOpenDatabaseException
import
android.database.sqlite.SQLiteCantOpenDatabaseException
import
com.github.shadowsocks.App.Companion.app
import
com.github.shadowsocks.ProfilesFragment
import
com.github.shadowsocks.ProfilesFragment
import
com.github.shadowsocks.preference.DataStore
import
com.github.shadowsocks.preference.DataStore
import
com.github.shadowsocks.utils.DirectBoot
import
com.github.shadowsocks.utils.DirectBoot
...
@@ -35,19 +34,8 @@ import java.sql.SQLException
...
@@ -35,19 +34,8 @@ import java.sql.SQLException
*/
*/
object
ProfileManager
{
object
ProfileManager
{
@Throws
(
SQLException
::
class
)
@Throws
(
SQLException
::
class
)
fun
createProfile
(
p
:
Profile
?
=
null
):
Profile
{
fun
createProfile
(
profile
:
Profile
=
Profile
()):
Profile
{
val
profile
=
p
?:
Profile
()
profile
.
id
=
0
profile
.
id
=
0
val
oldProfile
=
app
.
currentProfile
if
(
oldProfile
!=
null
)
{
// Copy Feature Settings from old profile
profile
.
route
=
oldProfile
.
route
profile
.
ipv6
=
oldProfile
.
ipv6
profile
.
proxyApps
=
oldProfile
.
proxyApps
profile
.
bypass
=
oldProfile
.
bypass
profile
.
individual
=
oldProfile
.
individual
profile
.
udpdns
=
oldProfile
.
udpdns
}
profile
.
userOrder
=
PrivateDatabase
.
profileDao
.
nextOrder
()
?:
0
profile
.
userOrder
=
PrivateDatabase
.
profileDao
.
nextOrder
()
?:
0
profile
.
id
=
PrivateDatabase
.
profileDao
.
create
(
profile
)
profile
.
id
=
PrivateDatabase
.
profileDao
.
create
(
profile
)
ProfilesFragment
.
instance
?.
profilesAdapter
?.
add
(
profile
)
ProfilesFragment
.
instance
?.
profilesAdapter
?.
add
(
profile
)
...
...
mobile/src/main/java/com/github/shadowsocks/utils/ArrayIterator.kt
0 → 100644
View file @
0055e53a
/*******************************************************************************
* *
* Copyright (C) 2018 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2018 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.utils
import
android.content.ClipData
import
androidx.recyclerview.widget.SortedList
import
org.json.JSONArray
private
sealed
class
ArrayIterator
<
out
T
>
:
Iterator
<
T
>
{
abstract
val
size
:
Int
abstract
operator
fun
get
(
index
:
Int
):
T
private
var
count
=
0
override
fun
hasNext
()
=
count
<
size
override
fun
next
():
T
=
if
(
hasNext
())
this
[
count
++]
else
throw
NoSuchElementException
()
}
private
class
ClipDataIterator
(
private
val
data
:
ClipData
)
:
ArrayIterator
<
ClipData
.
Item
>()
{
override
val
size
get
()
=
data
.
itemCount
override
fun
get
(
index
:
Int
)
=
data
.
getItemAt
(
index
)
}
fun
ClipData
.
asIterable
()
=
Iterable
{
ClipDataIterator
(
this
)
}
private
class
JSONArrayIterator
(
private
val
arr
:
JSONArray
)
:
ArrayIterator
<
Any
>()
{
override
val
size
get
()
=
arr
.
length
()
override
fun
get
(
index
:
Int
)
=
arr
.
get
(
index
)
}
fun
JSONArray
.
asIterable
()
=
Iterable
{
JSONArrayIterator
(
this
)
}
private
class
SortedListIterator
<
out
T
>(
private
val
list
:
SortedList
<
T
>)
:
ArrayIterator
<
T
>()
{
override
val
size
get
()
=
list
.
size
()
override
fun
get
(
index
:
Int
)
=
list
[
index
]
}
fun
<
T
>
SortedList
<
T
>.
asIterable
()
=
Iterable
{
SortedListIterator
(
this
)
}
mobile/src/main/java/com/github/shadowsocks/utils/Utils.kt
View file @
0055e53a
...
@@ -12,7 +12,6 @@ import android.net.Uri
...
@@ -12,7 +12,6 @@ import android.net.Uri
import
android.os.Build
import
android.os.Build
import
android.util.TypedValue
import
android.util.TypedValue
import
androidx.annotation.AttrRes
import
androidx.annotation.AttrRes
import
androidx.recyclerview.widget.SortedList
import
com.crashlytics.android.Crashlytics
import
com.crashlytics.android.Crashlytics
import
com.github.shadowsocks.JniHelper
import
com.github.shadowsocks.JniHelper
import
java.net.InetAddress
import
java.net.InetAddress
...
@@ -63,15 +62,7 @@ fun Resources.Theme.resolveResourceId(@AttrRes resId: Int): Int {
...
@@ -63,15 +62,7 @@ fun Resources.Theme.resolveResourceId(@AttrRes resId: Int): Int {
return
typedValue
.
resourceId
return
typedValue
.
resourceId
}
}
private
class
SortedListIterable
<
out
T
>(
private
val
list
:
SortedList
<
T
>)
:
Iterable
<
T
>
{
val
Intent
.
datas
get
()
=
listOfNotNull
(
data
)
+
(
clipData
?.
asIterable
()
?.
map
{
it
.
uri
}
?.
filterNotNull
()
?:
emptyList
())
override
fun
iterator
():
Iterator
<
T
>
=
SortedListIterator
(
list
)
}
private
class
SortedListIterator
<
out
T
>(
private
val
list
:
SortedList
<
T
>)
:
Iterator
<
T
>
{
private
var
count
=
0
override
fun
hasNext
()
=
count
<
list
.
size
()
override
fun
next
():
T
=
if
(
hasNext
())
list
[
count
++]
else
throw
NoSuchElementException
()
}
fun
<
T
>
SortedList
<
T
>.
asIterable
():
Iterable
<
T
>
=
SortedListIterable
(
this
)
fun
printLog
(
t
:
Throwable
)
{
fun
printLog
(
t
:
Throwable
)
{
Crashlytics
.
logException
(
t
)
Crashlytics
.
logException
(
t
)
...
...
mobile/src/main/res/drawable/ic_file_file_upload.xml
0 → 100644
View file @
0055e53a
<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=
"#FFF"
android:pathData=
"M9,16h6v-6h4l-7,-7 -7,7h4zM5,18h14v2L5,20z"
/>
</vector>
mobile/src/main/res/menu/app_manager_menu.xml
View file @
0055e53a
...
@@ -7,14 +7,14 @@
...
@@ -7,14 +7,14 @@
android:numericShortcut=
"1"
android:numericShortcut=
"1"
app:showAsAction=
"never"
/>
app:showAsAction=
"never"
/>
<item
<item
android:id=
"@+id/action_export"
android:id=
"@+id/action_export
_clipboard
"
android:alphabeticShortcut=
"e"
android:alphabeticShortcut=
"e"
android:icon=
"?attr/actionModeCopyDrawable"
android:icon=
"?attr/actionModeCopyDrawable"
android:numericShortcut=
"2"
android:numericShortcut=
"2"
android:title=
"@string/action_export"
android:title=
"@string/action_export"
app:showAsAction=
"ifRoom"
/>
app:showAsAction=
"ifRoom"
/>
<item
<item
android:id=
"@+id/action_import"
android:id=
"@+id/action_import
_clipboard
"
android:alphabeticShortcut=
"i"
android:alphabeticShortcut=
"i"
android:icon=
"?attr/actionModePasteDrawable"
android:icon=
"?attr/actionModePasteDrawable"
android:numericShortcut=
"3"
android:numericShortcut=
"3"
...
...
mobile/src/main/res/menu/custom_rules_menu.xml
View file @
0055e53a
...
@@ -11,7 +11,7 @@
...
@@ -11,7 +11,7 @@
android:title=
"@string/add_profile_methods_manual_settings"
android:title=
"@string/add_profile_methods_manual_settings"
android:alphabeticShortcut=
"m"
android:alphabeticShortcut=
"m"
android:numericShortcut=
"1"
/>
android:numericShortcut=
"1"
/>
<item
android:id=
"@+id/action_import"
<item
android:id=
"@+id/action_import
_clipboard
"
android:title=
"@string/action_import"
android:title=
"@string/action_import"
android:alphabeticShortcut=
"i"
android:alphabeticShortcut=
"i"
android:numericShortcut=
"2"
/>
android:numericShortcut=
"2"
/>
...
...
mobile/src/main/res/menu/profile_manager_menu.xml
View file @
0055e53a
...
@@ -14,22 +14,39 @@
...
@@ -14,22 +14,39 @@
android:numericShortcut=
"1"
android:numericShortcut=
"1"
android:title=
"@string/add_profile_methods_scan_qr_code"
/>
android:title=
"@string/add_profile_methods_scan_qr_code"
/>
<item
<item
android:id=
"@+id/action_import"
android:id=
"@+id/action_import
_clipboard
"
android:alphabeticShortcut=
"
i
"
android:alphabeticShortcut=
"
c
"
android:numericShortcut=
"2"
android:numericShortcut=
"2"
android:title=
"@string/action_import"
/>
android:title=
"@string/action_import"
/>
<item
android:id=
"@+id/action_import_file"
android:alphabeticShortcut=
"f"
android:numericShortcut=
"3"
android:title=
"@string/action_import_file"
/>
<item
<item
android:id=
"@+id/action_manual_settings"
android:id=
"@+id/action_manual_settings"
android:alphabeticShortcut=
"m"
android:alphabeticShortcut=
"m"
android:numericShortcut=
"
3
"
android:numericShortcut=
"
4
"
android:title=
"@string/add_profile_methods_manual_settings"
/>
android:title=
"@string/add_profile_methods_manual_settings"
/>
</menu>
</menu>
</item>
</item>
<item
<item
android:id=
"@+id/action_export"
android:alphabeticShortcut=
"e"
android:alphabeticShortcut=
"e"
android:icon=
"
?attr/actionModeCopyDrawable
"
android:icon=
"
@drawable/ic_file_file_upload
"
android:numericShortcut=
"1"
android:numericShortcut=
"1"
android:title=
"@string/action_export"
android:title=
"@string/action_export_more"
app:showAsAction=
"always"
/>
app:showAsAction=
"always"
>
<menu>
<item
android:id=
"@+id/action_export_clipboard"
android:alphabeticShortcut=
"c"
android:numericShortcut=
"1"
android:title=
"@string/action_export"
/>
<item
android:id=
"@+id/action_export_file"
android:alphabeticShortcut=
"f"
android:numericShortcut=
"2"
android:title=
"@string/action_export_file"
/>
</menu>
</item>
</menu>
</menu>
mobile/src/main/res/menu/profile_share_popup.xml
View file @
0055e53a
...
@@ -4,7 +4,7 @@
...
@@ -4,7 +4,7 @@
android:title=
"@string/share_qr_nfc"
android:title=
"@string/share_qr_nfc"
android:alphabeticShortcut=
"q"
android:alphabeticShortcut=
"q"
android:numericShortcut=
"1"
/>
android:numericShortcut=
"1"
/>
<item
android:id=
"@+id/action_export"
<item
android:id=
"@+id/action_export
_clipboard
"
android:title=
"@string/action_export"
android:title=
"@string/action_export"
android:alphabeticShortcut=
"e"
android:alphabeticShortcut=
"e"
android:numericShortcut=
"2"
/>
android:numericShortcut=
"2"
/>
...
...
mobile/src/main/res/menu/scanner_menu.xml
View file @
0055e53a
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android=
"http://schemas.android.com/apk/res/android"
<menu
xmlns:android=
"http://schemas.android.com/apk/res/android"
xmlns:app=
"http://schemas.android.com/apk/res-auto"
>
xmlns:app=
"http://schemas.android.com/apk/res-auto"
>
<item
android:id=
"@+id/action_import"
<item
android:id=
"@+id/action_import
_clipboard
"
android:title=
"@string/action_import_file"
android:title=
"@string/action_import_file"
android:icon=
"@drawable/ic_image_photo"
android:icon=
"@drawable/ic_image_photo"
android:alphabeticShortcut=
"p"
android:alphabeticShortcut=
"p"
...
...
mobile/src/main/res/values/strings.xml
View file @
0055e53a
...
@@ -93,6 +93,8 @@
...
@@ -93,6 +93,8 @@
<string
name=
"share"
>
Share
</string>
<string
name=
"share"
>
Share
</string>
<string
name=
"add_profile"
>
Add Profile
</string>
<string
name=
"add_profile"
>
Add Profile
</string>
<string
name=
"action_apply_all"
>
Apply Settings to All Profiles
</string>
<string
name=
"action_apply_all"
>
Apply Settings to All Profiles
</string>
<string
name=
"action_export_more"
>
Export…
</string>
<string
name=
"action_export_file"
>
Export to file…
</string>
<string
name=
"action_export"
>
Export to Clipboard
</string>
<string
name=
"action_export"
>
Export to Clipboard
</string>
<string
name=
"action_import"
>
Import from Clipboard
</string>
<string
name=
"action_import"
>
Import from Clipboard
</string>
<string
name=
"action_import_file"
>
Import from file…
</string>
<string
name=
"action_import_file"
>
Import from file…
</string>
...
...
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