Compare commits

..
38 Commits
Author SHA1 Message Date
Max Lv 3dff421867 Bump version 2018-03-29 19:39:19 -07:00
Mygod 6dadff9736 Use underlying network only since Android P
Fixes #1706.
2018-03-29 19:31:29 -07:00
Mygod 35d3707ff2 Update dependencies 2018-03-29 19:21:39 -07:00
Max Lv 43611850be Merge pull request #1696 from gam2046/master
Fix building with Windows. #1684
2018-03-29 09:19:23 +08:00
Max Lv ac4aeb5218 Bump version 2018-03-25 21:26:50 -07:00
Max Lv 86a2b3f1ab Merge pull request #1702 from initrider/master
Update translation of FAQ
2018-03-25 07:05:38 +08:00
initrider 74dad98fc1 Update faq.ru.md 2018-03-24 13:32:18 +03:00
initrider 01da6b1131 Merge pull request #3 from shadowsocks/master
Sync
2018-03-24 13:20:51 +03:00
Mozi 7c5840b288 Update faq.md (#1700)
It's not MIUI, but EMUI
2018-03-23 23:27:54 -07:00
Max Lv 24116b53bc Disable China IP list in overture 2018-03-24 09:41:44 +08:00
Mygod cffd9d1eb4 Fix usage stats not saved 2018-03-23 01:57:13 -07:00
Mygod 3c532f1ac5 Fix SQLiteDatabaseLockedException 2018-03-23 01:55:04 -07:00
Mygod c64e15acad Catch URISyntaxException 2018-03-23 01:32:25 -07:00
Mygod 9ff33ec1fd Fix IllegalArgumentException thrown by Base64.decode 2018-03-23 01:08:33 -07:00
Mygod a1aed680fd Fix IllegalStateException in Profile.findAll 2018-03-23 00:54:47 -07:00
Mygod dec2059a36 Fix NPE in primaryClip 2018-03-23 00:53:18 -07:00
Max Lv f7083025a0 Merge pull request #1698 from Mygod/underlying
Actually use underlying network
2018-03-23 15:51:03 +08:00
Mygod 74e2935f1e Actually use underlying network 2018-03-23 00:23:13 -07:00
Mygod 0af450008b Implement network capabilities reporting
This commit should fix the problem where VPN connection is viewed as metered connections on Android P DP1. However as I only have an emulator to test, I can't tell if it really works but it should.

An interesting observation is that the developer can call setUnderlyingNetworks(arrayOfNulls(1)) to make the VPN connection always unmetered. This is more of a quick hack instead of a real fix. Source: https://android.googlesource.com/platform/frameworks/base/+/adbf1d0/services/core/java/com/android/server/connectivity/Vpn.java#312

One may ask whether it's possible to fix this issue for Android 8.1. The short answer is not that I know of.
After some intensive source code reading, I found out that this is the commit that "breaks" whether VPN is metered for Android 8.1: https://android.googlesource.com/platform/frameworks/base/+/43d2a1700b6eb1d804924c6a1e5e0161a13a5348%5E%21/
The key is that isActiveNetworkMetered() used to return getActiveNetworkInfo().isMetered() and now it uses the more up-to-date API getActiveNetwork(). However, getActiveNetworkInfo() ignores VPN connections while getActiveNetwork() prefers VPN connection. And since NetworkCapabilities for VPN Network is always metered by default, this ultimately causes the bug. One might be able to get around this bug by somehow calling NetworkAgent.sendNetworkCapabilities in the correct context.

Fix #1666.
2018-03-22 23:40:15 -07:00
Mygod 75bc9334ef Fix #1691 2018-03-22 23:05:02 -07:00
forDream 207d76a143 elegant variables 2018-03-22 22:21:26 +08:00
forDream f79471407f Fix building with Windows. #1684
Signed-off-by: forDream <gam2046@users.noreply.github.com>
2018-03-22 15:36:26 +08:00
Mygod ec803d9859 Fix assert not working 2018-03-17 14:20:48 -07:00
Mygod f63842f773 Fix signature checks in ResolvedPlugin 2018-03-08 23:44:29 -08:00
Mygod f218282e28 Use require* in support library 2018-03-08 22:34:58 -08:00
Mygod 6d866633bf Update dependencies 2018-03-08 19:41:21 -08:00
initrider 633b9764c6 Removed unnecessary word 2018-02-02 21:58:04 +03:00
initrider 0643c60f0f Add #1589 and retranslate a bit of info 2018-02-02 21:56:37 +03:00
initrider 5a562ae343 Add #888 2018-02-02 21:48:12 +03:00
initrider 9838875996 Merge pull request #2 from shadowsocks/master
Sync
2018-02-02 21:43:53 +03:00
initrider 25aacd20d5 Visual improvement 2018-01-09 19:13:16 +03:00
initrider 7026ca99fc Update faq.ru.md 2018-01-09 19:10:52 +03:00
initrider 258a573e8c Fixed translation mistakes + Fix #1565 2018-01-09 19:06:22 +03:00
initrider 2b225dbc59 Merge pull request #1 from shadowsocks/master
Sync
2018-01-09 18:58:44 +03:00
Mygod c9b48af022 Rename .github/faq.ru-RU.md to .github/faq.ru.md 2018-01-04 21:07:45 +08:00
initrider 984a8c926e Translate - FAQ - Russian 2018-01-04 15:59:33 +03:00
initrider 5cf256f532 Update faq.ru-RU.md 2018-01-04 15:52:17 +03:00
initrider 5f4a4dc1e1 Create faq.ru-RU.md 2018-01-04 15:25:21 +03:00
28 changed files with 340 additions and 5257 deletions
+2 -1
View File
@@ -29,7 +29,8 @@ The exclamation mark in the Wi-Fi/cellular icon appears because the system fails
3. Some ROM like [Flyme](https://github.com/shadowsocks/shadowsocks-android/issues/1589) has **very** broken direct boot support;
4. If you have Xposed framework and/or battery saver apps, it's likely that this app wouldn't work well with these either.
* Fixes for MIUI: [#772](https://github.com/shadowsocks/shadowsocks-android/issues/772) [#888](https://github.com/shadowsocks/shadowsocks-android/issues/888)
* Fixes for MIUI: [#772](https://github.com/shadowsocks/shadowsocks-android/issues/772)
* Fixes for EMUI: [#888](https://github.com/shadowsocks/shadowsocks-android/issues/888)
* Fixes for Huawei: [#1091 (comment)](https://github.com/shadowsocks/shadowsocks-android/issues/1091#issuecomment-276949836)
* Related to Xposed: [#1414](https://github.com/shadowsocks/shadowsocks-android/issues/1414)
* Samsung and/or Brevent: [#1410](https://github.com/shadowsocks/shadowsocks-android/issues/1410)
+2 -6
View File
@@ -12,11 +12,6 @@
Использйте интеграцию с [Tasker](http://tasker.dinglisch.net/).
### Как добавить QR код из локальной галереи?
Отсканируйте его при помощи стороннего сканера, например [QuickMark Barcode Scanner](https://play.google.com/store/apps/details?id=tw.com.quickmark) и нажмите на `ss` ссылку.
### Почему режим NAT более не поддерживается?
1. Требуются ROOT права;
@@ -34,7 +29,8 @@
3. Некоторые ROM, такие как [Flyme](https://github.com/shadowsocks/shadowsocks-android/issues/1589) имеют **слишком** испорченную поддержку "непосредственной загрузки";
4. Если вы используете фреймворк Xposed и/или приложения для энергосбережения, вероятно наше приложение не будет нормально работать с ними.
* Иправления для MIUI: [#772](https://github.com/shadowsocks/shadowsocks-android/issues/772) [#888](https://github.com/shadowsocks/shadowsocks-android/issues/888)
* Исправления для MIUI: [#772](https://github.com/shadowsocks/shadowsocks-android/issues/772)
* Исправления для EMUI: [#888](https://github.com/shadowsocks/shadowsocks-android/issues/888)
* Иправления для Huawei: [#1091 (комментарий)](https://github.com/shadowsocks/shadowsocks-android/issues/1091#issuecomment-276949836)
* Вызванные из-за Xposed: [#1414](https://github.com/shadowsocks/shadowsocks-android/issues/1414)
* Samsung и/или Brevent: [#1410](https://github.com/shadowsocks/shadowsocks-android/issues/1410)
+4 -4
View File
@@ -4,13 +4,13 @@ apply plugin: 'com.github.ben-manes.versions'
buildscript {
ext {
kotlinVersion = '1.2.21'
kotlinVersion = '1.2.31'
minSdkVersion = 21
sdkVersion = 27
buildToolsVersion = '27.0.3'
supportLibraryVersion = '27.1.0'
takisoftFixVersion = '27.1.0.0'
playServicesVersion = '11.8.0'
playServicesVersion = '12.0.1'
junitVersion = '4.12'
androidTestVersion = '1.0.1'
androidEspressoVersion = '3.0.1'
@@ -22,9 +22,9 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.1'
classpath 'com.android.tools.build:gradle:3.1.0'
classpath 'com.github.ben-manes:gradle-versions-plugin:0.17.0'
classpath 'com.google.gms:google-services:3.2.0'
classpath 'com.google.gms:google-services:3.2.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
}
}
+2 -2
View File
@@ -38,7 +38,7 @@ android {
task goBuild(type: Exec) {
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
executable "cmd.exe"
args "/c", "src\\overture\\make.bat " + minSdkVersion
args "/c", file("src/overture/make.bat").absolutePath , minSdkVersion
} else {
executable "sh"
args "-c", "src/overture/make.bash " + minSdkVersion
@@ -48,7 +48,7 @@ task goBuild(type: Exec) {
task goClean(type: Exec) {
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
executable "cmd.exe"
args "/c", "src\\overture\\clean.bat"
args "/c", file("src/overture/clean.bat").absolutePath
} else {
executable "sh"
args "-c", "src/overture/clean.bash"
+18 -4
View File
@@ -28,21 +28,35 @@ MKDIR %TARGET%\armeabi-v7a>nul 2>nul
MKDIR %TARGET%\x86>nul 2>nul
MKDIR %TARGET%\arm64-v8a>nul 2>nul
SET CC=%ANDROID_ARM_TOOLCHAIN%\bin\arm-linux-androideabi-gcc.exe
REM Check environment availability
IF NOT EXIST %CC% (
ECHO "gcc not found"
EXIT 1
)
WHERE python.exe
IF "%ERRORLEVEL%" == 1 (
ECHO "python not found"
EXIT 1
)
IF NOT EXIST %ANDROID_ARM_CC% (
ECHO "Make standalone toolchain for ARM arch"
%ANDROID_NDK_HOME%\build\tools\make_standalone_toolchain.py --arch arm ^
python.exe %ANDROID_NDK_HOME%\build\tools\make_standalone_toolchain.py --arch arm ^
--api %MIN_API% --install-dir %ANDROID_ARM_TOOLCHAIN%
)
IF NOT EXIST %ANDROID_ARM64_CC% (
ECHO "Make standalone toolchain for ARM64 arch"
%ANDROID_NDK_HOME%\build\tools\make_standalone_toolchain.py --arch arm64 ^
python.exe %ANDROID_NDK_HOME%\build\tools\make_standalone_toolchain.py --arch arm64 ^
--api %MIN_API% --install-dir %ANDROID_ARM64_TOOLCHAIN%
)
IF NOT EXIST %ANDROID_X86_CC% (
ECHO "Make standalone toolchain for X86 arch"
%ANDROID_NDK_HOME%\build\tools\make_standalone_toolchain.py --arch x86 ^
python.exe %ANDROID_NDK_HOME%\build\tools\make_standalone_toolchain.py --arch x86 ^
--api %MIN_API% --install-dir %ANDROID_X86_TOOLCHAIN%
)
@@ -118,4 +132,4 @@ IF %BUILD% == 1 (
)
ECHO "Successfully build overture"
ENDLOCAL
ENDLOCAL
+5 -5
View File
@@ -21,8 +21,8 @@ android {
applicationId "com.github.shadowsocks"
minSdkVersion rootProject.minSdkVersion
targetSdkVersion rootProject.sdkVersion
versionCode 4050100
versionName "4.5.1"
versionCode 4050300
versionName "4.5.3"
testApplicationId "com.github.shadowsocks.test"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
resConfigs "fa", "fr", "ja", "ko", "ru", "zh-rCN", "zh-rTW"
@@ -58,14 +58,14 @@ dependencies {
implementation "com.android.support:design:$supportLibraryVersion"
implementation "com.android.support:gridlayout-v7:$supportLibraryVersion"
implementation 'com.futuremind.recyclerfastscroll:fastscroll:0.2.5'
implementation 'com.evernote:android-job:1.2.4'
implementation 'com.evernote:android-job:1.2.5'
implementation "com.google.android.gms:play-services-ads:$playServicesVersion"
implementation "com.google.android.gms:play-services-analytics:$playServicesVersion"
implementation "com.google.android.gms:play-services-vision:$playServicesVersion"
implementation "com.google.firebase:firebase-config:$playServicesVersion"
implementation 'com.j256.ormlite:ormlite-android:5.0'
implementation 'com.j256.ormlite:ormlite-android:5.1'
implementation 'com.mikepenz:materialdrawer:6.0.6'
implementation 'com.squareup.okhttp3:okhttp:3.9.1'
implementation 'com.squareup.okhttp3:okhttp:3.10.0'
implementation "com.takisoft.fix:preference-v7-simplemenu:$takisoftFixVersion"
implementation 'com.twofortyfouram:android-plugin-api-for-locale:1.0.2'
implementation 'dnsjava:dnsjava:2.1.8'
+1
View File
@@ -3,6 +3,7 @@
package="com.github.shadowsocks"
android:installLocation="internalOnly">
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.NFC" />
File diff suppressed because it is too large Load Diff
@@ -71,7 +71,10 @@ class App : Application() {
val deviceContext: Context by lazy { if (Build.VERSION.SDK_INT < 24) this else DeviceContext(this) }
val remoteConfig: FirebaseRemoteConfig by lazy { FirebaseRemoteConfig.getInstance() }
private val tracker: Tracker by lazy { GoogleAnalytics.getInstance(deviceContext).newTracker(R.xml.tracker) }
val info: PackageInfo by lazy { packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES) }
val info: PackageInfo by lazy { getPackageInfo(packageName) }
fun getPackageInfo(packageName: String) =
packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES)!!
fun startService() {
val intent = Intent(this, BaseService.serviceClass.java)
@@ -50,7 +50,7 @@ class GlobalSettingsPreferenceFragment : PreferenceFragmentCompatDividers() {
boot.isChecked = BootReceiver.enabled
val dba = findPreference(Key.directBootAware)
if (Build.VERSION.SDK_INT >= 24 && context!!.getSystemService(DevicePolicyManager::class.java)
if (Build.VERSION.SDK_INT >= 24 && requireContext().getSystemService(DevicePolicyManager::class.java)
.storageEncryptionStatus == DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE_PER_USER)
dba.onPreferenceChangeListener = directBootAwareListener else dba.parent!!.removePreference(dba)
@@ -59,7 +59,7 @@ class GlobalSettingsPreferenceFragment : PreferenceFragmentCompatDividers() {
tfo.setOnPreferenceChangeListener { _, value ->
val result = TcpFastOpen.enabled(value as Boolean)
if (result != null && result != "Success.")
Snackbar.make(activity!!.findViewById(R.id.snackbar), result, Snackbar.LENGTH_LONG).show()
Snackbar.make(requireActivity().findViewById(R.id.snackbar), result, Snackbar.LENGTH_LONG).show()
value == TcpFastOpen.sendEnabled
}
if (!TcpFastOpen.supported) {
@@ -56,7 +56,7 @@ class ProfileConfigFragment : PreferenceFragmentCompatDividers(), Toolbar.OnMenu
private const val REQUEST_CODE_PLUGIN_CONFIGURE = 1
}
private lateinit var profile: Profile
private var profileId = -1
private lateinit var isProxyApps: SwitchPreference
private lateinit var plugin: IconListPreference
private lateinit var pluginConfigure: EditTextPreference
@@ -65,14 +65,8 @@ class ProfileConfigFragment : PreferenceFragmentCompatDividers(), Toolbar.OnMenu
override fun onCreatePreferencesFix(savedInstanceState: Bundle?, rootKey: String?) {
preferenceManager.preferenceDataStore = DataStore.privateStore
val activity = activity!!
val profile = ProfileManager.getProfile(activity.intent.getIntExtra(Action.EXTRA_PROFILE_ID, -1))
if (profile == null) {
activity.finish()
return
}
this.profile = profile
profile.serialize()
val activity = requireActivity()
profileId = activity.intent.getIntExtra(Action.EXTRA_PROFILE_ID, -1)
addPreferencesFromResource(R.xml.pref_profile)
if (Build.VERSION.SDK_INT >= 25 && activity.getSystemService(UserManager::class.java).isDemoUser) {
findPreference(Key.host).summary = "shadowsocks.example.org"
@@ -130,11 +124,13 @@ class ProfileConfigFragment : PreferenceFragmentCompatDividers(), Toolbar.OnMenu
}
fun saveAndExit() {
val profile = ProfileManager.getProfile(profileId) ?: Profile()
profile.id = profileId
profile.deserialize()
ProfileManager.updateProfile(profile)
ProfilesFragment.instance?.profilesAdapter?.deepRefreshId(profile.id)
if (DataStore.profileId == profile.id && DataStore.directBootAware) DirectBoot.update()
activity!!.finish()
ProfilesFragment.instance?.profilesAdapter?.deepRefreshId(profileId)
if (DataStore.profileId == profileId && DataStore.directBootAware) DirectBoot.update()
requireActivity().finish()
}
override fun onResume() {
@@ -161,7 +157,7 @@ class ProfileConfigFragment : PreferenceFragmentCompatDividers(), Toolbar.OnMenu
override fun onDisplayPreferenceDialog(preference: Preference) {
if (preference.key == Key.pluginConfigure) {
val intent = PluginManager.buildIntent(pluginConfiguration.selected, PluginContract.ACTION_CONFIGURE)
if (intent.resolveActivity(activity!!.packageManager) != null)
if (intent.resolveActivity(requireContext().packageManager) != null)
startActivityForResult(intent.putExtra(PluginContract.EXTRA_OPTIONS,
pluginConfiguration.selectedOptions.toString()), REQUEST_CODE_PLUGIN_CONFIGURE) else {
showPluginEditor()
@@ -183,11 +179,11 @@ class ProfileConfigFragment : PreferenceFragmentCompatDividers(), Toolbar.OnMenu
override fun onMenuItemClick(item: MenuItem) = when (item.itemId) {
R.id.action_delete -> {
val activity = activity!!
val activity = requireActivity()
AlertDialog.Builder(activity)
.setTitle(R.string.delete_confirm_prompt)
.setPositiveButton(R.string.yes, { _, _ ->
ProfileManager.delProfile(profile.id)
ProfileManager.delProfile(profileId)
activity.finish()
})
.setNegativeButton(R.string.no, null)
@@ -123,12 +123,12 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
private var adView: AdView? = null
init {
edit.setOnClickListener { startConfig(item.id) }
edit.setOnClickListener { startConfig(item) }
TooltipCompat.setTooltipText(edit, edit.contentDescription)
itemView.setOnClickListener(this)
val share = itemView.findViewById<View>(R.id.share)
share.setOnClickListener {
val popup = PopupMenu(activity!!, share)
val popup = PopupMenu(requireContext(), share)
popup.menuInflater.inflate(R.menu.profile_share_popup, popup.menu)
popup.setOnMenuItemClickListener(this)
popup.show()
@@ -173,10 +173,10 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
var adView = adView
if (item.host == "198.199.101.152") {
if (adView == null) {
val params =
LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
val params = LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT)
params.gravity = Gravity.CENTER_HORIZONTAL
val context = context!!
val context = requireContext()
adView = AdView(context)
adView.layoutParams = params
adView.adUnitId = "ca-app-pub-9097031975646651/7760346322"
@@ -208,7 +208,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
override fun onMenuItemClick(item: MenuItem): Boolean = when (item.itemId) {
R.id.action_qr_code_nfc -> {
fragmentManager!!.beginTransaction().add(QRCodeDialog(this.item.toString()), "")
requireFragmentManager().beginTransaction().add(QRCodeDialog(this.item.toString()), "")
.commitAllowingStateLoss()
true
}
@@ -230,7 +230,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
override fun onBindViewHolder(holder: ProfileViewHolder, position: Int) = holder.bind(profiles[position])
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProfileViewHolder = ProfileViewHolder(
LayoutInflater.from(parent!!.context).inflate(R.layout.layout_profile, parent, false))
LayoutInflater.from(parent.context).inflate(R.layout.layout_profile, parent, false))
override fun getItemCount(): Int = profiles.size
override fun getItemId(position: Int): Long = profiles[position].id.toLong()
@@ -305,10 +305,12 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
private var txTotal: Long = 0L
private var rxTotal: Long = 0L
private val clipboard by lazy { activity!!.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager }
private val clipboard by lazy { requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager }
private fun startConfig(id: Int) = startActivity(Intent(context, ProfileConfigActivity::class.java)
.putExtra(Action.EXTRA_PROFILE_ID, id))
private fun startConfig(profile: Profile) {
profile.serialize()
startActivity(Intent(context, ProfileConfigActivity::class.java).putExtra(Action.EXTRA_PROFILE_ID, profile.id))
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
inflater.inflate(R.layout.layout_list, container, false)
@@ -331,7 +333,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
profilesList.itemAnimator = animator
profilesList.adapter = profilesAdapter
instance = this
undoManager = UndoSnackbarManager(activity!!.findViewById(R.id.snackbar),
undoManager = UndoSnackbarManager(requireActivity().findViewById(R.id.snackbar),
profilesAdapter::undo, profilesAdapter::commit)
ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP or ItemTouchHelper.DOWN,
ItemTouchHelper.START or ItemTouchHelper.END) {
@@ -351,7 +353,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
profilesAdapter.move(viewHolder.adapterPosition, target.adapterPosition)
return true
}
override fun clearView(recyclerView: RecyclerView?, viewHolder: RecyclerView.ViewHolder?) {
override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
super.clearView(recyclerView, viewHolder)
profilesAdapter.commitMove()
}
@@ -366,25 +368,25 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
}
R.id.action_import -> {
try {
val profiles = Profile.findAll(clipboard.primaryClip.getItemAt(0).text).toList()
val profiles = Profile.findAll(clipboard.primaryClip!!.getItemAt(0).text).toList()
if (profiles.isNotEmpty()) {
profiles.forEach { ProfileManager.createProfile(it) }
Snackbar.make(activity!!.findViewById(R.id.snackbar), R.string.action_import_msg,
Snackbar.make(requireActivity().findViewById(R.id.snackbar), R.string.action_import_msg,
Snackbar.LENGTH_LONG).show()
return true
}
} catch (_: IndexOutOfBoundsException) { }
Snackbar.make(activity!!.findViewById(R.id.snackbar), R.string.action_import_err, Snackbar.LENGTH_LONG)
.show()
} catch (_: Exception) { }
Snackbar.make(requireActivity().findViewById(R.id.snackbar), R.string.action_import_err,
Snackbar.LENGTH_LONG).show()
true
}
R.id.action_manual_settings -> {
startConfig(ProfileManager.createProfile().id)
startConfig(ProfileManager.createProfile())
true
}
R.id.action_export -> {
val profiles = ProfileManager.getAllProfiles()
Snackbar.make(activity!!.findViewById(R.id.snackbar), if (profiles != null) {
Snackbar.make(requireActivity().findViewById(R.id.snackbar), if (profiles != null) {
clipboard.primaryClip = ClipData.newPlainText(null, profiles.joinToString("\n"))
R.string.action_export_msg
} else R.string.action_export_err, Snackbar.LENGTH_LONG).show()
@@ -80,7 +80,7 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener,
private lateinit var positive: Button
init {
val activity = activity!!
val activity = requireActivity()
val view = activity.layoutInflater.inflate(R.layout.dialog_acl_rule, null)
templateSelector = view.findViewById(R.id.template_selector)
editText = view.findViewById(R.id.content)
@@ -360,7 +360,7 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener,
private lateinit var list: RecyclerView
private var mode: ActionMode? = null
private lateinit var undoManager: UndoSnackbarManager<Any>
private val clipboard by lazy { activity!!.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager }
private val clipboard by lazy { requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager }
private fun onSelectedItemsUpdated() {
if (selectedItems.isEmpty()) mode?.finish() else if (mode == null) mode = toolbar.startActionMode(this)
@@ -383,12 +383,13 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener,
toolbar.setTitle(R.string.custom_rules)
toolbar.inflateMenu(R.menu.custom_rules_menu)
toolbar.setOnMenuItemClickListener(this)
val activity = requireActivity()
list = view.findViewById(R.id.list)
list.layoutManager = LinearLayoutManager(activity, LinearLayoutManager.VERTICAL, false)
list.itemAnimator = DefaultItemAnimator()
list.adapter = adapter
view.findViewById<FastScroller>(R.id.fastscroller).setRecyclerView(list)
undoManager = UndoSnackbarManager(activity!!.findViewById(R.id.snackbar), adapter::undo)
undoManager = UndoSnackbarManager(activity.findViewById(R.id.snackbar), adapter::undo)
ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.START or ItemTouchHelper.END) {
override fun getSwipeDirs(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int =
if (isEnabled && selectedItems.isEmpty()) super.getSwipeDirs(recyclerView, viewHolder) else 0
@@ -437,10 +438,10 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener,
}
R.id.action_import -> {
try {
adapter.addToProxy(clipboard.primaryClip.getItemAt(0).text.toString()) != null
check(adapter.addToProxy(clipboard.primaryClip!!.getItemAt(0).text.toString()) != null)
} catch (exc: Exception) {
Snackbar.make(activity!!.findViewById(R.id.snackbar), R.string.action_import_err, Snackbar.LENGTH_LONG)
.show()
Snackbar.make(requireActivity().findViewById(R.id.snackbar), R.string.action_import_err,
Snackbar.LENGTH_LONG).show()
app.track(exc)
}
true
@@ -462,7 +463,7 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener,
}
override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
val activity = activity!!
val activity = requireActivity()
val window = activity.window
// In the end material_grey_100 is used for background, see AppCompatDrawableManager (very complicated)
if (Build.VERSION.SDK_INT >= 23) {
@@ -495,7 +496,7 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener,
else -> false
}
override fun onDestroyActionMode(mode: ActionMode) {
val activity = activity!!
val activity = requireActivity()
val window = activity.window
window.statusBarColor = ContextCompat.getColor(activity,
activity.theme.resolveResourceId(android.R.attr.statusBarColor))
@@ -84,7 +84,6 @@ object LocalDnsService {
Acl.BYPASS_CHN, Acl.BYPASS_LAN_CHN, Acl.GFWLIST, Acl.CUSTOM_RULES -> config
.put("PrimaryDNS", localDns)
.put("AlternativeDNS", remoteDns)
.put("IPNetworkFile", "china_ip_list.txt")
.put("DomainFile", data.aclFile!!.absolutePath)
Acl.CHINALIST -> config
.put("PrimaryDNS", localDns)
@@ -20,12 +20,16 @@
package com.github.shadowsocks.bg
import android.annotation.TargetApi
import android.app.Service
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.LocalSocket
import android.net.*
import android.os.Build
import android.os.IBinder
import android.os.ParcelFileDescriptor
import android.support.v4.os.BuildCompat
import android.util.Log
import com.github.shadowsocks.App.Companion.app
import com.github.shadowsocks.JniHelper
@@ -50,6 +54,21 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
private const val PRIVATE_VLAN6 = "fdfe:dcba:9876::%s"
private val getInt: Method = FileDescriptor::class.java.getDeclaredMethod("getInt$")
/**
* Unfortunately registerDefaultNetworkCallback is going to return VPN interface since Android P DP1:
* https://android.googlesource.com/platform/frameworks/base/+/dda156ab0c5d66ad82bdcf76cda07cbc0a9c8a2e
*
* This makes doing a requestNetwork with REQUEST necessary so that we don't get ALL possible networks that
* satisfies default network capabilities but only THE default network. Unfortunately we need to have
* android.permission.CHANGE_NETWORK_STATE to be able to call requestNetwork.
*
* Source: https://android.googlesource.com/platform/frameworks/base/+/2df4c7d/services/core/java/com/android/server/ConnectivityService.java#887
*/
private val defaultNetworkRequest = NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
.build()
}
private inner class ProtectWorker : LocalSocketListener("ShadowsocksVpnThread") {
@@ -59,13 +78,22 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
var success = false
try {
socket.inputStream.read()
val fds = socket.ancillaryFileDescriptors
if (fds.isEmpty()) return
val fd = getInt.invoke(fds.first()) as Int
success = protect(fd)
JniHelper.close(fd) // Trick to close file decriptor
val fd = socket.ancillaryFileDescriptors!!.single()!!
val fdInt = getInt.invoke(fd) as Int
try {
val network = underlyingNetwork
success = if (network != null && Build.VERSION.SDK_INT >= 23) {
network.bindSocket(fd)
true
} else protect(fdInt)
} catch (e: Exception) {
Log.e(tag, "Error when protect socket", e)
app.track(e)
} finally {
JniHelper.close(fdInt) // Trick to close file decriptor
}
} catch (e: Exception) {
Log.e(tag, "Error when protect socket", e)
Log.e(tag, "Error when receiving ancillary fd", e)
app.track(e)
}
try {
@@ -89,6 +117,28 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
private var conn: ParcelFileDescriptor? = null
private var worker: ProtectWorker? = null
private var tun2socksProcess: GuardedProcess? = null
private var underlyingNetwork: Network? = null
@TargetApi(28)
set(value) {
setUnderlyingNetworks(if (value == null) null else arrayOf(value))
field = value
}
private val connectivity by lazy { getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager }
@TargetApi(28)
private val defaultNetworkCallback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
underlyingNetwork = network
}
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities?) {
// it's a good idea to refresh capabilities
underlyingNetwork = network
}
override fun onLost(network: Network) {
underlyingNetwork = null
}
}
private var listeningForDefaultNetwork = false
override fun onBind(intent: Intent): IBinder? = when (intent.action) {
SERVICE_INTERFACE -> super<BaseVpnService>.onBind(intent)
@@ -98,6 +148,10 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
override fun onRevoke() = stopRunner(true)
override fun killProcesses() {
if (listeningForDefaultNetwork) {
connectivity.unregisterNetworkCallback(defaultNetworkCallback)
listeningForDefaultNetwork = false
}
worker?.stopThread()
worker = null
super.killProcesses()
@@ -179,6 +233,12 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
this.conn = conn
val fd = conn.fd
if (BuildCompat.isAtLeastP()) {
// we want REQUEST here instead of LISTEN
connectivity.requestNetwork(defaultNetworkRequest, defaultNetworkCallback)
listeningForDefaultNetwork = true
}
val cmd = arrayListOf(File(applicationInfo.nativeLibraryDir, Executable.TUN2SOCKS).absolutePath,
"--netif-ipaddr", PRIVATE_VLAN.format(Locale.ENGLISH, "2"),
"--netif-netmask", "255.255.255.0",
@@ -0,0 +1,61 @@
/*******************************************************************************
* *
* 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.database
import android.database.sqlite.SQLiteDatabaseLockedException
import com.j256.ormlite.dao.Dao
import com.j256.ormlite.support.ConnectionSource
import com.j256.ormlite.table.TableUtils
import java.sql.SQLException
private val Throwable.ultimateCause: Throwable get() {
var result = this
while (true) {
val cause = result.cause ?: return result
result = cause
}
}
@Throws(SQLException::class)
fun <T> safeWrapper(func: () -> T): T {
while (true) {
try {
return func()
} catch (e: SQLException) {
if (e.ultimateCause !is SQLiteDatabaseLockedException) throw e
}
}
}
@Throws(SQLException::class)
inline fun <reified T> ConnectionSource.createTableSafe() = safeWrapper { TableUtils.createTable(this, T::class.java) }
@Throws(SQLException::class)
fun <T, ID> Dao<T, ID>.queryAllSafe(): MutableList<T> = safeWrapper { queryForAll() }
@Throws(SQLException::class)
fun <T, ID> Dao<T, ID>.queryByIdSafe(id: ID?): T? = safeWrapper { queryForId(id) }
@Throws(SQLException::class)
fun <T, ID> Dao<T, ID>.updateSafe(data: T?): Int = safeWrapper { update(data) }
@Throws(SQLException::class)
fun <T, ID> Dao<T, ID>.replaceSafe(data: T?): Dao.CreateOrUpdateStatus? = safeWrapper { createOrUpdate(data) }
@Throws(SQLException::class)
fun <T, ID> Dao<T, ID>.deleteByIdSafe(id: ID?) = safeWrapper { deleteById(id) }
@Throws(SQLException::class)
fun <T, ID> Dao<T, ID>.executeSafe(statement: String?) = safeWrapper { executeRawNoArgs(statement) }
@@ -37,18 +37,18 @@ object PrivateDatabase : OrmLiteSqliteOpenHelper(app, Key.DB_PROFILE, null, 25)
@Suppress("UNCHECKED_CAST")
val kvPairDao: Dao<KeyValuePair, String?> by lazy { getDao(KeyValuePair::class.java) as Dao<KeyValuePair, String?> }
override fun onCreate(database: SQLiteDatabase?, connectionSource: ConnectionSource?) {
TableUtils.createTable(connectionSource, Profile::class.java)
TableUtils.createTable(connectionSource, KeyValuePair::class.java)
override fun onCreate(database: SQLiteDatabase?, connectionSource: ConnectionSource) {
connectionSource.createTableSafe<Profile>()
connectionSource.createTableSafe<KeyValuePair>()
}
private fun recreate(database: SQLiteDatabase?, connectionSource: ConnectionSource?) {
private fun recreate(database: SQLiteDatabase?, connectionSource: ConnectionSource) {
TableUtils.dropTable<Profile, Int>(connectionSource, Profile::class.java, true)
TableUtils.dropTable<KeyValuePair, String?>(connectionSource, KeyValuePair::class.java, true)
onCreate(database, connectionSource)
}
override fun onUpgrade(database: SQLiteDatabase?, connectionSource: ConnectionSource?,
override fun onUpgrade(database: SQLiteDatabase?, connectionSource: ConnectionSource,
oldVersion: Int, newVersion: Int) {
if (oldVersion < 7) {
recreate(database, connectionSource)
@@ -57,38 +57,38 @@ object PrivateDatabase : OrmLiteSqliteOpenHelper(app, Key.DB_PROFILE, null, 25)
try {
if (oldVersion < 8) {
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN udpdns SMALLINT;")
profileDao.executeSafe("ALTER TABLE `profile` ADD COLUMN udpdns SMALLINT;")
}
if (oldVersion < 9) {
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN route VARCHAR DEFAULT 'all';")
profileDao.executeSafe("ALTER TABLE `profile` ADD COLUMN route VARCHAR DEFAULT 'all';")
} else if (oldVersion < 19) {
profileDao.executeRawNoArgs("UPDATE `profile` SET route = 'all' WHERE route IS NULL;")
profileDao.executeSafe("UPDATE `profile` SET route = 'all' WHERE route IS NULL;")
}
if (oldVersion < 11) {
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN ipv6 SMALLINT;")
profileDao.executeSafe("ALTER TABLE `profile` ADD COLUMN ipv6 SMALLINT;")
}
if (oldVersion < 12) {
profileDao.executeRawNoArgs("BEGIN TRANSACTION;")
profileDao.executeRawNoArgs("ALTER TABLE `profile` RENAME TO `tmp`;")
TableUtils.createTable(connectionSource, Profile::class.java)
profileDao.executeRawNoArgs(
profileDao.executeSafe("BEGIN TRANSACTION;")
profileDao.executeSafe("ALTER TABLE `profile` RENAME TO `tmp`;")
connectionSource.createTableSafe<Profile>()
profileDao.executeSafe(
"INSERT INTO `profile`(id, name, host, localPort, remotePort, password, method, route," +
" proxyApps, bypass, udpdns, ipv6, individual) " +
"SELECT id, name, host, localPort, remotePort, password, method, route, 1 - global," +
" bypass, udpdns, ipv6, individual FROM `tmp`;")
profileDao.executeRawNoArgs("DROP TABLE `tmp`;")
profileDao.executeRawNoArgs("COMMIT;")
profileDao.executeSafe("DROP TABLE `tmp`;")
profileDao.executeSafe("COMMIT;")
} else if (oldVersion < 13) {
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN tx LONG;")
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN rx LONG;")
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN date VARCHAR;")
profileDao.executeSafe("ALTER TABLE `profile` ADD COLUMN tx LONG;")
profileDao.executeSafe("ALTER TABLE `profile` ADD COLUMN rx LONG;")
profileDao.executeSafe("ALTER TABLE `profile` ADD COLUMN date VARCHAR;")
}
if (oldVersion < 15) {
if (oldVersion >= 12) profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN userOrder LONG;")
if (oldVersion >= 12) profileDao.executeSafe("ALTER TABLE `profile` ADD COLUMN userOrder LONG;")
var i = 0L
val apps by lazy { app.packageManager.getInstalledApplications(0) }
for (profile in profileDao.queryForAll()) {
for (profile in profileDao.queryAllSafe()) {
if (oldVersion < 14) {
val uidSet = profile.individual.split('|').filter(TextUtils::isDigitsOnly)
.map(String::toInt).toSet()
@@ -96,28 +96,28 @@ object PrivateDatabase : OrmLiteSqliteOpenHelper(app, Key.DB_PROFILE, null, 25)
.joinToString("\n") { it.packageName }
}
profile.userOrder = i
profileDao.update(profile)
profileDao.updateSafe(profile)
i += 1
}
}
if (oldVersion < 16) {
profileDao.executeRawNoArgs(
profileDao.executeSafe(
"UPDATE `profile` SET route = 'bypass-lan-china' WHERE route = 'bypass-china'")
}
if (oldVersion < 21) {
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN remoteDns VARCHAR DEFAULT '8.8.8.8';")
profileDao.executeSafe("ALTER TABLE `profile` ADD COLUMN remoteDns VARCHAR DEFAULT '8.8.8.8';")
}
if (oldVersion < 17) {
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN plugin VARCHAR;")
profileDao.executeSafe("ALTER TABLE `profile` ADD COLUMN plugin VARCHAR;")
} else if (oldVersion < 22) {
// upgrade kcptun to SIP003 plugin
profileDao.executeRawNoArgs("BEGIN TRANSACTION;")
profileDao.executeRawNoArgs("ALTER TABLE `profile` RENAME TO `tmp`;")
TableUtils.createTable(connectionSource, Profile::class.java)
profileDao.executeRawNoArgs(
profileDao.executeSafe("BEGIN TRANSACTION;")
profileDao.executeSafe("ALTER TABLE `profile` RENAME TO `tmp`;")
connectionSource.createTableSafe<Profile>()
profileDao.executeSafe(
"INSERT INTO `profile`(id, name, host, localPort, remotePort, password, method, route, " +
"remoteDns, proxyApps, bypass, udpdns, ipv6, individual, tx, rx, date, userOrder, " +
"plugin) " +
@@ -125,17 +125,17 @@ object PrivateDatabase : OrmLiteSqliteOpenHelper(app, Key.DB_PROFILE, null, 25)
"CASE WHEN kcp = 1 THEN kcpPort ELSE remotePort END, password, method, route, " +
"remoteDns, proxyApps, bypass, udpdns, ipv6, individual, tx, rx, date, userOrder, " +
"CASE WHEN kcp = 1 THEN 'kcptun ' || kcpcli ELSE NULL END FROM `tmp`;")
profileDao.executeRawNoArgs("DROP TABLE `tmp`;")
profileDao.executeRawNoArgs("COMMIT;")
profileDao.executeSafe("DROP TABLE `tmp`;")
profileDao.executeSafe("COMMIT;")
}
if (oldVersion < 23) {
profileDao.executeRawNoArgs("BEGIN TRANSACTION;")
TableUtils.createTable(connectionSource, KeyValuePair::class.java)
profileDao.executeRawNoArgs("COMMIT;")
profileDao.executeSafe("BEGIN TRANSACTION;")
connectionSource.createTableSafe<KeyValuePair>()
profileDao.executeSafe("COMMIT;")
val old = PreferenceManager.getDefaultSharedPreferences(app)
PublicDatabase.kvPairDao.createOrUpdate(KeyValuePair(Key.id).put(old.getInt(Key.id, 0)))
PublicDatabase.kvPairDao.createOrUpdate(KeyValuePair(Key.tfo).put(old.getBoolean(Key.tfo, false)))
PublicDatabase.kvPairDao.replaceSafe(KeyValuePair(Key.id).put(old.getInt(Key.id, 0)))
PublicDatabase.kvPairDao.replaceSafe(KeyValuePair(Key.tfo).put(old.getBoolean(Key.tfo, false)))
}
if (oldVersion < 25) {
@@ -31,6 +31,7 @@ import com.j256.ormlite.field.DataType
import com.j256.ormlite.field.DatabaseField
import java.io.Serializable
import java.net.URI
import java.net.URISyntaxException
import java.util.*
class Profile : Serializable {
@@ -43,41 +44,51 @@ class Profile : Serializable {
fun findAll(data: CharSequence?) = pattern.findAll(data ?: "").map {
val uri = Uri.parse(it.value)
if (uri.userInfo == null) {
val match = legacyPattern.matchEntire(String(Base64.decode(uri.host, Base64.NO_PADDING)))
if (match != null) {
val profile = Profile()
profile.method = match.groupValues[1].toLowerCase()
profile.password = match.groupValues[2]
profile.host = match.groupValues[3]
profile.remotePort = match.groupValues[4].toInt()
profile.plugin = uri.getQueryParameter(Key.plugin)
profile.name = uri.fragment
profile
try {
if (uri.userInfo == null) {
val match = legacyPattern.matchEntire(String(Base64.decode(uri.host, Base64.NO_PADDING)))
if (match != null) {
val profile = Profile()
profile.method = match.groupValues[1].toLowerCase()
profile.password = match.groupValues[2]
profile.host = match.groupValues[3]
profile.remotePort = match.groupValues[4].toInt()
profile.plugin = uri.getQueryParameter(Key.plugin)
profile.name = uri.fragment
profile
} else {
Log.e(TAG, "Unrecognized URI: ${it.value}")
null
}
} else {
Log.e(TAG, "Unrecognized URI: ${it.value}")
null
}
} else {
val match = userInfoPattern.matchEntire(String(Base64.decode(uri.userInfo,
Base64.NO_PADDING or Base64.NO_WRAP or Base64.URL_SAFE)))
if (match != null) {
val profile = Profile()
profile.method = match.groupValues[1]
profile.password = match.groupValues[2]
// bug in Android: https://code.google.com/p/android/issues/detail?id=192855
val javaURI = URI(it.value)
profile.host = javaURI.host
if (profile.host.firstOrNull() == '[' && profile.host.lastOrNull() == ']')
profile.host = profile.host.substring(1, profile.host.length - 1)
profile.remotePort = javaURI.port
profile.plugin = uri.getQueryParameter(Key.plugin)
profile.name = uri.fragment ?: ""
profile
} else {
Log.e(TAG, "Unknown user info: ${it.value}")
null
val match = userInfoPattern.matchEntire(String(Base64.decode(uri.userInfo,
Base64.NO_PADDING or Base64.NO_WRAP or Base64.URL_SAFE)))
if (match != null) {
val profile = Profile()
profile.method = match.groupValues[1]
profile.password = match.groupValues[2]
// bug in Android: https://code.google.com/p/android/issues/detail?id=192855
try {
val javaURI = URI(it.value)
profile.host = javaURI.host ?: ""
if (profile.host.firstOrNull() == '[' && profile.host.lastOrNull() == ']')
profile.host = profile.host.substring(1, profile.host.length - 1)
profile.remotePort = javaURI.port
profile.plugin = uri.getQueryParameter(Key.plugin)
profile.name = uri.fragment ?: ""
profile
} catch (e: URISyntaxException) {
Log.e(TAG, "Invalid URI: ${it.value}")
null
}
} else {
Log.e(TAG, "Unknown user info: ${it.value}")
null
}
}
} catch (e: IllegalArgumentException) {
Log.e(TAG, "Invalid base64 detected: ${it.value}")
null
}
}.filterNotNull()
}
@@ -48,10 +48,12 @@ object ProfileManager {
profile.individual = oldProfile.individual
profile.udpdns = oldProfile.udpdns
}
val last = PrivateDatabase.profileDao.queryRaw(PrivateDatabase.profileDao.queryBuilder()
val last = safeWrapper {
PrivateDatabase.profileDao.queryRaw(PrivateDatabase.profileDao.queryBuilder()
.selectRaw("MAX(userOrder)").prepareStatementString()).firstResult
}
if (last != null && last.size == 1 && last[0] != null) profile.userOrder = last[0].toLong() + 1
PrivateDatabase.profileDao.createOrUpdate(profile)
PrivateDatabase.profileDao.replaceSafe(profile)
ProfilesFragment.instance?.profilesAdapter?.add(profile)
return profile
}
@@ -60,10 +62,10 @@ object ProfileManager {
* Note: It's caller's responsibility to update DirectBoot profile if necessary.
*/
@Throws(SQLException::class)
fun updateProfile(profile: Profile) = PrivateDatabase.profileDao.update(profile)
fun updateProfile(profile: Profile) = PrivateDatabase.profileDao.updateSafe(profile)
fun getProfile(id: Int): Profile? = try {
PrivateDatabase.profileDao.queryForId(id)
PrivateDatabase.profileDao.queryByIdSafe(id)
} catch (ex: SQLException) {
Log.e(TAG, "getProfile", ex)
app.track(ex)
@@ -72,13 +74,16 @@ object ProfileManager {
@Throws(SQLException::class)
fun delProfile(id: Int) {
PrivateDatabase.profileDao.deleteById(id)
PrivateDatabase.profileDao.deleteByIdSafe(id)
ProfilesFragment.instance?.profilesAdapter?.removeId(id)
if (id == DataStore.profileId && DataStore.directBootAware) DirectBoot.clean()
}
fun getFirstProfile(): Profile? = try {
PrivateDatabase.profileDao.query(PrivateDatabase.profileDao.queryBuilder().limit(1L).prepare()).singleOrNull()
safeWrapper {
PrivateDatabase.profileDao.query(
PrivateDatabase.profileDao.queryBuilder().limit(1L).prepare()).singleOrNull()
}
} catch (ex: SQLException) {
Log.e(TAG, "getFirstProfile", ex)
app.track(ex)
@@ -86,7 +91,10 @@ object ProfileManager {
}
fun getAllProfiles(): List<Profile>? = try {
PrivateDatabase.profileDao.query(PrivateDatabase.profileDao.queryBuilder().orderBy("userOrder", true).prepare())
safeWrapper {
PrivateDatabase.profileDao.query(
PrivateDatabase.profileDao.queryBuilder().orderBy("userOrder", true).prepare())
}
} catch (ex: SQLException) {
Log.e(TAG, "getAllProfiles", ex)
app.track(ex)
@@ -34,20 +34,21 @@ object PublicDatabase : OrmLiteSqliteOpenHelper(app.deviceContext, Key.DB_PUBLIC
@Suppress("UNCHECKED_CAST")
val kvPairDao: Dao<KeyValuePair, String?> by lazy { getDao(KeyValuePair::class.java) as Dao<KeyValuePair, String?> }
override fun onCreate(database: SQLiteDatabase?, connectionSource: ConnectionSource?) {
TableUtils.createTable(connectionSource, KeyValuePair::class.java)
override fun onCreate(database: SQLiteDatabase?, connectionSource: ConnectionSource) {
connectionSource.createTableSafe<KeyValuePair>()
}
override fun onUpgrade(database: SQLiteDatabase?, connectionSource: ConnectionSource?,
oldVersion: Int, newVersion: Int) {
if (oldVersion < 1) {
PrivateDatabase.kvPairDao.queryBuilder().where().`in`("key",
safeWrapper {
PrivateDatabase.kvPairDao.queryBuilder().where().`in`("key",
Key.id, Key.tfo, Key.serviceMode, Key.portProxy, Key.portLocalDns, Key.portTransproxy).query()
.forEach { kvPairDao.createOrUpdate(it) }
}.forEach { kvPairDao.replaceSafe(it) }
}
if (oldVersion < 2) {
kvPairDao.createOrUpdate(KeyValuePair(Acl.CUSTOM_RULES).put(Acl().fromId(Acl.CUSTOM_RULES).toString()))
kvPairDao.replaceSafe(KeyValuePair(Acl.CUSTOM_RULES).put(Acl().fromId(Acl.CUSTOM_RULES).toString()))
}
}
@@ -25,7 +25,7 @@ import android.os.Bundle
class NativePlugin(resolveInfo: ResolveInfo) : ResolvedPlugin(resolveInfo) {
init {
assert(resolveInfo.providerInfo != null)
check(resolveInfo.providerInfo != null)
}
override val metaData: Bundle get() = resolveInfo.providerInfo.metaData
@@ -134,7 +134,7 @@ object PluginManager {
private fun initNative(options: PluginOptions): String? {
val providers = app.packageManager.queryIntentContentProviders(
Intent(PluginContract.ACTION_NATIVE_PLUGIN, buildUri(options.id)), 0)
assert(providers.size == 1)
check(providers.size == 1)
val uri = Uri.Builder()
.scheme(ContentResolver.SCHEME_CONTENT)
.authority(providers[0].providerInfo.authority)
@@ -154,7 +154,7 @@ object PluginManager {
out.putString(PluginContract.EXTRA_OPTIONS, options.id)
val result = cr.call(uri, PluginContract.METHOD_GET_EXECUTABLE, null, out)
.getString(PluginContract.EXTRA_ENTRY)
assert(File(result).canExecute())
check(File(result).canExecute())
return result
}
@@ -173,7 +173,7 @@ object PluginManager {
do {
val path = cursor.getString(0)
val file = File(pluginDir, path)
assert(file.absolutePath.startsWith(pluginDirPath))
check(file.absolutePath.startsWith(pluginDirPath))
cr.openInputStream(uri.buildUpon().path(path).build()).use { inStream ->
file.outputStream().use { outStream -> inStream.copyTo(outStream) }
}
@@ -33,5 +33,7 @@ abstract class ResolvedPlugin(protected val resolveInfo: ResolveInfo) : Plugin()
override val icon: Drawable by lazy { resolveInfo.loadIcon(app.packageManager) }
override val defaultConfig: String by lazy { metaData.getString(PluginContract.METADATA_KEY_DEFAULT_CONFIG) }
override val packageName: String get() = resolveInfo.resolvePackageName
override val trusted by lazy { app.info.signatures.any(PluginManager.trustedSignatures::contains) }
override val trusted by lazy {
app.getPackageInfo(packageName).signatures.any(PluginManager.trustedSignatures::contains)
}
}
@@ -101,8 +101,8 @@ class BottomSheetPreferenceDialogFragment : PreferenceDialogFragmentCompat() {
private var clickedIndex = -1
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val activity = activity
val dialog = BottomSheetDialog(activity!!, theme)
val activity = requireActivity()
val dialog = BottomSheetDialog(activity, theme)
val recycler = RecyclerView(activity)
val padding = resources.getDimensionPixelOffset(R.dimen.bottom_sheet_padding)
recycler.setPadding(0, padding, 0, padding)
@@ -22,17 +22,20 @@ package com.github.shadowsocks.preference
import android.support.v7.preference.PreferenceDataStore
import com.github.shadowsocks.database.KeyValuePair
import com.github.shadowsocks.database.deleteByIdSafe
import com.github.shadowsocks.database.queryByIdSafe
import com.github.shadowsocks.database.replaceSafe
import com.j256.ormlite.dao.Dao
import java.util.HashSet
@Suppress("MemberVisibilityCanPrivate", "unused")
open class OrmLitePreferenceDataStore(private val kvPairDao: Dao<KeyValuePair, String?>) : PreferenceDataStore() {
fun getBoolean(key: String?) = kvPairDao.queryForId(key)?.boolean
fun getFloat(key: String?) = kvPairDao.queryForId(key)?.float
fun getInt(key: String?) = kvPairDao.queryForId(key)?.int
fun getLong(key: String?) = kvPairDao.queryForId(key)?.long
fun getString(key: String?) = kvPairDao.queryForId(key)?.string
fun getStringSet(key: String?) = kvPairDao.queryForId(key)?.stringSet
fun getBoolean(key: String?) = kvPairDao.queryByIdSafe(key)?.boolean
fun getFloat(key: String?) = kvPairDao.queryByIdSafe(key)?.float
fun getInt(key: String?) = kvPairDao.queryByIdSafe(key)?.int
fun getLong(key: String?) = kvPairDao.queryByIdSafe(key)?.long
fun getString(key: String?) = kvPairDao.queryByIdSafe(key)?.string
fun getStringSet(key: String?) = kvPairDao.queryByIdSafe(key)?.stringSet
override fun getBoolean(key: String?, defValue: Boolean) = getBoolean(key) ?: defValue
override fun getFloat(key: String?, defValue: Float) = getFloat(key) ?: defValue
@@ -46,32 +49,32 @@ open class OrmLitePreferenceDataStore(private val kvPairDao: Dao<KeyValuePair, S
fun putInt(key: String?, value: Int?) = if (value == null) remove(key) else putInt(key, value)
fun putLong(key: String?, value: Long?) = if (value == null) remove(key) else putLong(key, value)
override fun putBoolean(key: String?, value: Boolean) {
kvPairDao.createOrUpdate(KeyValuePair(key).put(value))
kvPairDao.replaceSafe(KeyValuePair(key).put(value))
fireChangeListener(key)
}
override fun putFloat(key: String?, value: Float) {
kvPairDao.createOrUpdate(KeyValuePair(key).put(value))
kvPairDao.replaceSafe(KeyValuePair(key).put(value))
fireChangeListener(key)
}
override fun putInt(key: String?, value: Int) {
kvPairDao.createOrUpdate(KeyValuePair(key).put(value))
kvPairDao.replaceSafe(KeyValuePair(key).put(value))
fireChangeListener(key)
}
override fun putLong(key: String?, value: Long) {
kvPairDao.createOrUpdate(KeyValuePair(key).put(value))
kvPairDao.replaceSafe(KeyValuePair(key).put(value))
fireChangeListener(key)
}
override fun putString(key: String?, value: String?) = if (value == null) remove(key) else {
kvPairDao.createOrUpdate(KeyValuePair(key).put(value))
kvPairDao.replaceSafe(KeyValuePair(key).put(value))
fireChangeListener(key)
}
override fun putStringSet(key: String?, values: MutableSet<String>?) = if (values == null) remove(key) else {
kvPairDao.createOrUpdate(KeyValuePair(key).put(values))
kvPairDao.replaceSafe(KeyValuePair(key).put(values))
fireChangeListener(key)
}
fun remove(key: String?) {
kvPairDao.deleteById(key)
kvPairDao.deleteByIdSafe(key)
fireChangeListener(key)
}
@@ -40,8 +40,9 @@ class PluginConfigurationDialogFragment : EditTextPreferenceDialogFragmentCompat
super.onPrepareDialogBuilder(builder)
val intent = PluginManager.buildIntent(arguments!!.getString(PLUGIN_ID_FRAGMENT_TAG),
PluginContract.ACTION_HELP)
if (intent.resolveActivity(activity!!.packageManager) != null) builder.setNeutralButton("?", { _, _ ->
activity!!.startActivityForResult(intent.putExtra(PluginContract.EXTRA_OPTIONS, editText.text.toString()),
val activity = requireActivity()
if (intent.resolveActivity(activity.packageManager) != null) builder.setNeutralButton("?", { _, _ ->
activity.startActivityForResult(intent.putExtra(PluginContract.EXTRA_OPTIONS, editText.text.toString()),
ProfileConfigActivity.REQUEST_CODE_PLUGIN_HELP)
})
}
@@ -61,7 +61,7 @@ abstract class NativePluginProvider : ContentProvider() {
override fun query(uri: Uri, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?,
sortOrder: String?): Cursor {
assert(selection == null && selectionArgs == null && sortOrder == null)
check(selection == null && selectionArgs == null && sortOrder == null)
val result = MatrixCursor(projection)
populateFiles(PathProvider(uri, result))
return result
@@ -79,7 +79,7 @@ abstract class NativePluginProvider : ContentProvider() {
abstract fun openFile(uri: Uri?): ParcelFileDescriptor
override fun openFile(uri: Uri?, mode: String?): ParcelFileDescriptor {
assert(mode == "r")
check(mode == "r")
return openFile(uri)
}