Fix code style issues
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
[](https://travis-ci.org/shadowsocks/shadowsocks-android)
|
||||
[](https://android-arsenal.com/api?level=21)
|
||||
[](https://github.com/shadowsocks/shadowsocks-android/releases)
|
||||
[](https://www.codacy.com/app/shadowsocks/shadowsocks-android?utm_source=github.com&utm_medium=referral&utm_content=shadowsocks/shadowsocks-android&utm_campaign=Badge_Grade)
|
||||
[](https://www.gnu.org/licenses/gpl-3.0)
|
||||
|
||||
A [shadowsocks](http://shadowsocks.org) client for Android, written in Kotlin.
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
#!/usr/bin/python
|
||||
# -*- encoding: utf8 -*-
|
||||
|
||||
import itertools
|
||||
import math
|
||||
import sys
|
||||
|
||||
import IPy
|
||||
|
||||
+1
-2
@@ -28,8 +28,7 @@ complexity:
|
||||
threshold: 5
|
||||
ignoreDefaultParameters: true
|
||||
MethodOverloading:
|
||||
active: true
|
||||
threshold: 5
|
||||
active: false
|
||||
NestedBlockDepth:
|
||||
active: true
|
||||
threshold: 3
|
||||
|
||||
@@ -44,7 +44,6 @@ import android.support.v7.widget.Toolbar
|
||||
import android.view.*
|
||||
import android.widget.ImageView
|
||||
import android.widget.Switch
|
||||
import android.widget.Toast
|
||||
import com.futuremind.recyclerviewfastscroll.FastScroller
|
||||
import com.futuremind.recyclerviewfastscroll.SectionTitleProvider
|
||||
import com.github.shadowsocks.App.Companion.app
|
||||
@@ -264,11 +263,9 @@ class AppManager : AppCompatActivity(), Toolbar.OnMenuItemClickListener {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
|
||||
return if (keyCode == KeyEvent.KEYCODE_MENU)
|
||||
if (toolbar.isOverflowMenuShowing) toolbar.hideOverflowMenu() else toolbar.showOverflowMenu()
|
||||
else super.onKeyUp(keyCode, event)
|
||||
}
|
||||
override fun onKeyUp(keyCode: Int, event: KeyEvent?) = if (keyCode == KeyEvent.KEYCODE_MENU)
|
||||
if (toolbar.isOverflowMenuShowing) toolbar.hideOverflowMenu() else toolbar.showOverflowMenu()
|
||||
else super.onKeyUp(keyCode, event)
|
||||
|
||||
override fun onDestroy() {
|
||||
instance = null
|
||||
|
||||
@@ -26,7 +26,6 @@ import android.os.Bundle
|
||||
import android.support.design.widget.Snackbar
|
||||
import android.support.v14.preference.SwitchPreference
|
||||
import android.support.v7.preference.Preference
|
||||
import com.github.shadowsocks.App.Companion.app
|
||||
import com.github.shadowsocks.bg.BaseService
|
||||
import com.github.shadowsocks.preference.DataStore
|
||||
import com.github.shadowsocks.utils.DirectBoot
|
||||
|
||||
@@ -62,6 +62,7 @@ import com.mikepenz.materialdrawer.Drawer
|
||||
import com.mikepenz.materialdrawer.DrawerBuilder
|
||||
import com.mikepenz.materialdrawer.model.PrimaryDrawerItem
|
||||
import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem
|
||||
import java.io.IOException
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.InetSocketAddress
|
||||
import java.net.Proxy
|
||||
@@ -178,8 +179,8 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Interface, Drawe
|
||||
val elapsed = SystemClock.elapsedRealtime() - start
|
||||
if (code == 204 || code == 200 && conn.responseLength == 0L)
|
||||
Pair(true, getString(R.string.connection_test_available, elapsed))
|
||||
else throw Exception(getString(R.string.connection_test_error_status_code, code))
|
||||
} catch (e: Exception) {
|
||||
else throw IOException(getString(R.string.connection_test_error_status_code, code))
|
||||
} catch (e: IOException) {
|
||||
Pair(false, getString(R.string.connection_test_error, e.message))
|
||||
} finally {
|
||||
conn.disconnect()
|
||||
|
||||
@@ -179,27 +179,25 @@ class ProfileConfigFragment : PreferenceFragmentCompatDividers(), Toolbar.OnMenu
|
||||
} else super.onActivityResult(requestCode, resultCode, data)
|
||||
}
|
||||
|
||||
override fun onMenuItemClick(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.action_delete -> {
|
||||
val activity = activity!!
|
||||
AlertDialog.Builder(activity)
|
||||
.setTitle(R.string.delete_confirm_prompt)
|
||||
.setPositiveButton(R.string.yes, { _, _ ->
|
||||
ProfileManager.delProfile(profile.id)
|
||||
activity.finish()
|
||||
})
|
||||
.setNegativeButton(R.string.no, null)
|
||||
.create()
|
||||
.show()
|
||||
true
|
||||
}
|
||||
R.id.action_apply -> {
|
||||
saveAndExit()
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
override fun onMenuItemClick(item: MenuItem) = when (item.itemId) {
|
||||
R.id.action_delete -> {
|
||||
val activity = activity!!
|
||||
AlertDialog.Builder(activity)
|
||||
.setTitle(R.string.delete_confirm_prompt)
|
||||
.setPositiveButton(R.string.yes, { _, _ ->
|
||||
ProfileManager.delProfile(profile.id)
|
||||
activity.finish()
|
||||
})
|
||||
.setNegativeButton(R.string.no, null)
|
||||
.create()
|
||||
.show()
|
||||
true
|
||||
}
|
||||
R.id.action_apply -> {
|
||||
saveAndExit()
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
|
||||
@@ -37,7 +37,6 @@ import android.view.*
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import com.github.shadowsocks.App.Companion.app
|
||||
import com.github.shadowsocks.bg.BaseService
|
||||
import com.github.shadowsocks.bg.TrafficMonitor
|
||||
@@ -374,9 +373,7 @@ class ProfilesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener {
|
||||
Snackbar.LENGTH_LONG).show()
|
||||
return true
|
||||
}
|
||||
} catch (exc: Exception) {
|
||||
app.track(exc)
|
||||
}
|
||||
} catch (_: IndexOutOfBoundsException) { }
|
||||
Snackbar.make(activity!!.findViewById(R.id.snackbar), R.string.action_import_err, Snackbar.LENGTH_LONG)
|
||||
.show()
|
||||
true
|
||||
|
||||
@@ -139,31 +139,29 @@ class ScannerActivity : AppCompatActivity(), ZXingScannerView.ResultHandler, Too
|
||||
.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true), if (manual) REQUEST_IMPORT else REQUEST_IMPORT_OR_FINISH)
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
when (requestCode) {
|
||||
REQUEST_IMPORT, REQUEST_IMPORT_OR_FINISH -> {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
var list = listOfNotNull(data?.data)
|
||||
val clipData = data?.clipData
|
||||
if (clipData != null) list += (0 until clipData.itemCount).map { clipData.getItemAt(it).uri }
|
||||
val resolver = contentResolver
|
||||
val reader = MultiFormatReader()
|
||||
var success = false
|
||||
for (uri in list) try {
|
||||
val bitmap = BitmapFactory.decodeStream(resolver.openInputStream(uri))
|
||||
val pixels = IntArray(bitmap.width * bitmap.height)
|
||||
bitmap.getPixels(pixels, 0, bitmap.width, 0, 0, bitmap.width, bitmap.height)
|
||||
Profile.findAll(reader.decode(BinaryBitmap(HybridBinarizer(
|
||||
RGBLuminanceSource(bitmap.width, bitmap.height, pixels)))).text).forEach {
|
||||
ProfileManager.createProfile(it)
|
||||
success = true
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
app.track(e)
|
||||
REQUEST_IMPORT, REQUEST_IMPORT_OR_FINISH -> if (resultCode == Activity.RESULT_OK) {
|
||||
var list = listOfNotNull(data?.data)
|
||||
val clipData = data?.clipData
|
||||
if (clipData != null) list += (0 until clipData.itemCount).map { clipData.getItemAt(it).uri }
|
||||
val resolver = contentResolver
|
||||
val reader = MultiFormatReader()
|
||||
var success = false
|
||||
for (uri in list) try {
|
||||
val bitmap = BitmapFactory.decodeStream(resolver.openInputStream(uri))
|
||||
val pixels = IntArray(bitmap.width * bitmap.height)
|
||||
bitmap.getPixels(pixels, 0, bitmap.width, 0, 0, bitmap.width, bitmap.height)
|
||||
Profile.findAll(reader.decode(BinaryBitmap(HybridBinarizer(
|
||||
RGBLuminanceSource(bitmap.width, bitmap.height, pixels)))).text).forEach {
|
||||
ProfileManager.createProfile(it)
|
||||
success = true
|
||||
}
|
||||
Toast.makeText(this, if (success) R.string.action_import_msg else R.string.action_import_err,
|
||||
Toast.LENGTH_SHORT).show()
|
||||
finish()
|
||||
} else if (requestCode == REQUEST_IMPORT_OR_FINISH) finish()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
app.track(e)
|
||||
}
|
||||
Toast.makeText(this, if (success) R.string.action_import_msg else R.string.action_import_err,
|
||||
Toast.LENGTH_SHORT).show()
|
||||
finish()
|
||||
} else if (requestCode == REQUEST_IMPORT_OR_FINISH) finish()
|
||||
else -> super.onActivityResult(requestCode, resultCode, data)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener,
|
||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { }
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { }
|
||||
override fun afterTextChanged(s: Editable) = validate(value = s)
|
||||
override fun onNothingSelected(parent: AdapterView<*>?) = throw IllegalStateException()
|
||||
override fun onNothingSelected(parent: AdapterView<*>?) = check(false)
|
||||
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) = validate(position)
|
||||
|
||||
private fun validate(template: Int = templateSelector.selectedItemPosition, value: Editable = editText.text) {
|
||||
@@ -228,16 +228,14 @@ class CustomRulesFragment : ToolbarFragment(), Toolbar.OnMenuItemClickListener,
|
||||
override fun getItemCount(): Int = acl.subnets.size() + acl.hostnames.size() + acl.urls.size()
|
||||
override fun getSectionTitle(i: Int): String {
|
||||
val j = i - acl.subnets.size()
|
||||
return try {
|
||||
(if (j < 0) acl.subnets[i].address.hostAddress.substring(0, 1) else {
|
||||
val k = j - acl.hostnames.size()
|
||||
if (k < 0) {
|
||||
val hostname = acl.hostnames[j]
|
||||
// don't convert IDN yet
|
||||
PATTERN_DOMAIN.find(hostname)?.value?.replace("\\.", ".") ?: hostname
|
||||
} else acl.urls[k].host
|
||||
}).substring(0, 1)
|
||||
} catch (_: IndexOutOfBoundsException) { " " }
|
||||
return (if (j < 0) acl.subnets[i].address.hostAddress.substring(0, 1) else {
|
||||
val k = j - acl.hostnames.size()
|
||||
if (k < 0) {
|
||||
val hostname = acl.hostnames[j]
|
||||
// don't convert IDN yet
|
||||
PATTERN_DOMAIN.find(hostname)?.value?.replace("\\.", ".") ?: hostname
|
||||
} else acl.urls[k].host
|
||||
}).firstOrNull()?.toString() ?: " "
|
||||
}
|
||||
|
||||
private fun apply() {
|
||||
|
||||
@@ -31,7 +31,6 @@ import android.os.RemoteCallbackList
|
||||
import android.support.v4.os.UserManagerCompat
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import com.github.shadowsocks.App.Companion.app
|
||||
import com.github.shadowsocks.R
|
||||
import com.github.shadowsocks.acl.Acl
|
||||
@@ -146,18 +145,18 @@ object BaseService {
|
||||
}
|
||||
|
||||
internal fun updateTrafficTotal(tx: Long, rx: Long) {
|
||||
val profile = profile ?: return
|
||||
val p = ProfileManager.getProfile(profile.id) ?: return // profile may have host, etc. modified
|
||||
p.tx += tx
|
||||
p.rx += rx
|
||||
ProfileManager.updateProfile(p)
|
||||
// this.profile may have host, etc. modified and thus a re-fetch is necessary (possible race condition)
|
||||
val profile = ProfileManager.getProfile((profile ?: return).id) ?: return
|
||||
profile.tx += tx
|
||||
profile.rx += rx
|
||||
ProfileManager.updateProfile(profile)
|
||||
app.handler.post {
|
||||
if (bandwidthListeners.isNotEmpty()) {
|
||||
val n = callbacks.beginBroadcast()
|
||||
for (i in 0 until n) {
|
||||
try {
|
||||
val item = callbacks.getBroadcastItem(i)
|
||||
if (bandwidthListeners.contains(item.asBinder())) item.trafficPersisted(p.id)
|
||||
if (bandwidthListeners.contains(item.asBinder())) item.trafficPersisted(profile.id)
|
||||
} catch (_: Exception) { } // ignore
|
||||
}
|
||||
callbacks.finishBroadcast()
|
||||
@@ -182,9 +181,9 @@ object BaseService {
|
||||
.put("plugin_opts", plugin.toString())
|
||||
}
|
||||
// sensitive Shadowsocks config is stored in
|
||||
val file = File((if (UserManagerCompat.isUserUnlocked(app)) app.filesDir else @TargetApi(24) {
|
||||
val file = File(if (UserManagerCompat.isUserUnlocked(app)) app.filesDir else @TargetApi(24) {
|
||||
app.deviceContext.noBackupFilesDir // only API 24+ will be in locked state
|
||||
}), CONFIG_FILE)
|
||||
}, CONFIG_FILE)
|
||||
shadowsocksConfigFile = file
|
||||
file.writeText(config.toString())
|
||||
return file
|
||||
@@ -218,9 +217,9 @@ object BaseService {
|
||||
false
|
||||
} else true
|
||||
fun forceLoad() {
|
||||
val p = app.currentProfile
|
||||
val profile = app.currentProfile
|
||||
?: return stopRunner(true, (this as Context).getString(R.string.profile_empty))
|
||||
if (!checkProfile(p)) return
|
||||
if (!checkProfile(profile)) return
|
||||
val s = data.state
|
||||
when (s) {
|
||||
STOPPED -> startRunner()
|
||||
|
||||
@@ -24,22 +24,19 @@ import android.util.Log
|
||||
import com.github.shadowsocks.App.Companion.app
|
||||
import com.github.shadowsocks.BuildConfig
|
||||
import org.xbill.DNS.*
|
||||
import java.net.Inet6Address
|
||||
import java.net.InetAddress
|
||||
import java.net.NetworkInterface
|
||||
import java.net.UnknownHostException
|
||||
import okhttp3.Dns as Okdns
|
||||
import java.io.IOException
|
||||
import java.net.*
|
||||
|
||||
object Dns {
|
||||
private const val TAG = "Dns"
|
||||
|
||||
private val hasIPv6Support get() = try {
|
||||
val result = NetworkInterface.getNetworkInterfaces().asSequence().flatMap { it.inetAddresses.asSequence() }
|
||||
.count { it is Inet6Address && !it.isLoopbackAddress && !it.isLinkLocalAddress } > 0
|
||||
.any { it is Inet6Address && !it.isLoopbackAddress && !it.isLinkLocalAddress }
|
||||
if (result && BuildConfig.DEBUG) Log.d(TAG, "IPv6 address detected")
|
||||
result
|
||||
} catch (ex: Exception) {
|
||||
Log.e(TAG, "Failed to get interfaces' addresses.")
|
||||
} catch (ex: SocketException) {
|
||||
Log.e(TAG, "Failed to get interfaces' addresses.", ex)
|
||||
app.track(ex)
|
||||
false
|
||||
}
|
||||
@@ -60,7 +57,7 @@ object Dns {
|
||||
Type.AAAA -> return (r as AAAARecord).address.hostAddress
|
||||
}
|
||||
}
|
||||
} catch (_: Exception) { }
|
||||
} catch (_: IOException) { }
|
||||
return null
|
||||
}
|
||||
private fun resolve(host: String): String? = try {
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
|
||||
package com.github.shadowsocks.bg
|
||||
|
||||
import android.content.Context
|
||||
import com.github.shadowsocks.App.Companion.app
|
||||
import com.github.shadowsocks.acl.Acl
|
||||
import com.github.shadowsocks.preference.DataStore
|
||||
@@ -50,10 +49,10 @@ object LocalDnsService {
|
||||
fun makeDns(name: String, address: String, timeout: Int, edns: Boolean = true): JSONObject {
|
||||
val dns = JSONObject()
|
||||
.put("Name", name)
|
||||
.put("Address", (when (address.parseNumericAddress()) {
|
||||
.put("Address", when (address.parseNumericAddress()) {
|
||||
is Inet6Address -> "[$address]"
|
||||
else -> address
|
||||
}))
|
||||
})
|
||||
.put("Timeout", timeout)
|
||||
.put("EDNSClientSubnet", JSONObject().put("Policy", "disable"))
|
||||
if (edns) dns
|
||||
|
||||
@@ -23,7 +23,6 @@ package com.github.shadowsocks.bg
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.os.IBinder
|
||||
import com.github.shadowsocks.database.Profile
|
||||
|
||||
/**
|
||||
* Shadowsocks service at its minimum.
|
||||
|
||||
@@ -44,7 +44,7 @@ object TrafficMonitor {
|
||||
private val units = arrayOf("KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB", "BB", "NB", "DB", "CB")
|
||||
private val numberFormat = DecimalFormat("@@@")
|
||||
fun formatTraffic(size: Long): String {
|
||||
var n: Double = size.toDouble()
|
||||
var n = size.toDouble()
|
||||
var i = -1
|
||||
while (n >= 999.5) {
|
||||
n /= 1024
|
||||
|
||||
@@ -38,7 +38,7 @@ class TrafficMonitorThread : LocalSocketListener("TrafficMonitorThread") {
|
||||
val stat = ByteBuffer.wrap(buffer).order(ByteOrder.LITTLE_ENDIAN)
|
||||
TrafficMonitor.update(stat.getLong(0), stat.getLong(8))
|
||||
socket.outputStream.write(0)
|
||||
} catch (e: Exception) {
|
||||
} catch (e: IOException) {
|
||||
Log.e(tag, "Error when recv traffic stat", e)
|
||||
app.track(e)
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ import com.github.shadowsocks.utils.Subnet
|
||||
import com.github.shadowsocks.utils.parseNumericAddress
|
||||
import java.io.File
|
||||
import java.io.FileDescriptor
|
||||
import java.io.IOException
|
||||
import java.lang.reflect.Method
|
||||
import java.util.*
|
||||
import android.net.VpnService as BaseVpnService
|
||||
@@ -55,18 +56,24 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
|
||||
override val socketFile: File = File(app.deviceContext.filesDir, "protect_path")
|
||||
|
||||
override fun accept(socket: LocalSocket) {
|
||||
var success = false
|
||||
try {
|
||||
socket.inputStream.read()
|
||||
val fds = socket.ancillaryFileDescriptors
|
||||
if (fds.isEmpty()) return
|
||||
val fd = getInt.invoke(fds.first()) as Int
|
||||
val ret = protect(fd)
|
||||
success = protect(fd)
|
||||
JniHelper.close(fd) // Trick to close file decriptor
|
||||
socket.outputStream.write(if (ret) 0 else 1)
|
||||
} catch (e: Exception) {
|
||||
Log.e(tag, "Error when protect socket", e)
|
||||
app.track(e)
|
||||
}
|
||||
try {
|
||||
socket.outputStream.write(if (success) 0 else 1)
|
||||
} catch (e: IOException) {
|
||||
Log.e(tag, "Error when returning result in protect", e)
|
||||
app.track(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
class NullConnectionException : NullPointerException()
|
||||
@@ -118,7 +125,7 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
|
||||
super.startNativeProcesses()
|
||||
|
||||
val fd = startVpn()
|
||||
if (!sendFd(fd)) throw Exception("sendFd failed")
|
||||
if (!sendFd(fd)) throw IOException("sendFd failed")
|
||||
}
|
||||
|
||||
override fun buildAdditionalArguments(cmd: ArrayList<String>): ArrayList<String> {
|
||||
|
||||
@@ -72,10 +72,10 @@ object PrivateDatabase : OrmLiteSqliteOpenHelper(app, Key.DB_PROFILE, null, 25)
|
||||
profileDao.executeRawNoArgs("ALTER TABLE `profile` RENAME TO `tmp`;")
|
||||
TableUtils.createTable(connectionSource, Profile::class.java)
|
||||
profileDao.executeRawNoArgs(
|
||||
"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`;")
|
||||
"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;")
|
||||
} else if (oldVersion < 13) {
|
||||
|
||||
@@ -36,6 +36,7 @@ import java.util.*
|
||||
class Profile : Serializable {
|
||||
companion object {
|
||||
private const val TAG = "ShadowParser"
|
||||
private const val serialVersionUID = 0L
|
||||
private val pattern = """(?i)ss://[-a-zA-Z0-9+&@#/%?=~_|!:,.;\[\]]*[-a-zA-Z0-9+&@#/%=~_|\[\]]""".toRegex()
|
||||
private val userInfoPattern = "^(.+?):(.*)$".toRegex()
|
||||
private val legacyPattern = "^(.+?):(.*)@(.+?):(\\d+?)$".toRegex()
|
||||
|
||||
@@ -25,79 +25,69 @@ import com.github.shadowsocks.App.Companion.app
|
||||
import com.github.shadowsocks.ProfilesFragment
|
||||
import com.github.shadowsocks.preference.DataStore
|
||||
import com.github.shadowsocks.utils.DirectBoot
|
||||
import java.sql.SQLException
|
||||
|
||||
/**
|
||||
* SQLExceptions are not caught (and therefore will cause crash) for insert/update transactions
|
||||
* to ensure we are in a consistent state.
|
||||
*/
|
||||
object ProfileManager {
|
||||
private const val TAG = "ProfileManager"
|
||||
|
||||
@Throws(SQLException::class)
|
||||
fun createProfile(p: Profile? = null): Profile {
|
||||
val profile = p ?: Profile()
|
||||
profile.id = 0
|
||||
try {
|
||||
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
|
||||
}
|
||||
val last = 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)
|
||||
ProfilesFragment.instance?.profilesAdapter?.add(profile)
|
||||
} catch (ex: Exception) {
|
||||
Log.e(TAG, "addProfile", ex)
|
||||
app.track(ex)
|
||||
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
|
||||
}
|
||||
val last = 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)
|
||||
ProfilesFragment.instance?.profilesAdapter?.add(profile)
|
||||
return profile
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: It's caller's responsibility to update DirectBoot profile if necessary.
|
||||
*/
|
||||
fun updateProfile(profile: Profile): Boolean = try {
|
||||
PrivateDatabase.profileDao.update(profile)
|
||||
true
|
||||
} catch (ex: Exception) {
|
||||
Log.e(TAG, "updateProfile", ex)
|
||||
app.track(ex)
|
||||
false
|
||||
}
|
||||
@Throws(SQLException::class)
|
||||
fun updateProfile(profile: Profile) = PrivateDatabase.profileDao.update(profile)
|
||||
|
||||
fun getProfile(id: Int): Profile? = try {
|
||||
PrivateDatabase.profileDao.queryForId(id)
|
||||
} catch (ex: Exception) {
|
||||
} catch (ex: SQLException) {
|
||||
Log.e(TAG, "getProfile", ex)
|
||||
app.track(ex)
|
||||
null
|
||||
}
|
||||
|
||||
fun delProfile(id: Int): Boolean = try {
|
||||
@Throws(SQLException::class)
|
||||
fun delProfile(id: Int) {
|
||||
PrivateDatabase.profileDao.deleteById(id)
|
||||
ProfilesFragment.instance?.profilesAdapter?.removeId(id)
|
||||
if (id == DataStore.profileId && DataStore.directBootAware) DirectBoot.clean()
|
||||
true
|
||||
} catch (ex: Exception) {
|
||||
Log.e(TAG, "delProfile", ex)
|
||||
app.track(ex)
|
||||
false
|
||||
}
|
||||
|
||||
fun getFirstProfile(): Profile? = try {
|
||||
val result = PrivateDatabase.profileDao.query(PrivateDatabase.profileDao.queryBuilder().limit(1L).prepare())
|
||||
if (result.size == 1) result[0] else null
|
||||
} catch (ex: Exception) {
|
||||
Log.e(TAG, "getAllProfiles", ex)
|
||||
PrivateDatabase.profileDao.query(PrivateDatabase.profileDao.queryBuilder().limit(1L).prepare()).singleOrNull()
|
||||
} catch (ex: SQLException) {
|
||||
Log.e(TAG, "getFirstProfile", ex)
|
||||
app.track(ex)
|
||||
null
|
||||
}
|
||||
|
||||
fun getAllProfiles(): List<Profile>? = try {
|
||||
PrivateDatabase.profileDao.query(PrivateDatabase.profileDao.queryBuilder().orderBy("userOrder", true).prepare())
|
||||
} catch (ex: Exception) {
|
||||
} catch (ex: SQLException) {
|
||||
Log.e(TAG, "getAllProfiles", ex)
|
||||
app.track(ex)
|
||||
null
|
||||
|
||||
@@ -52,7 +52,7 @@ object DataStore {
|
||||
/**
|
||||
* Setter is defined in MainActivity.onPreferenceDataStoreChanged.
|
||||
*/
|
||||
val directBootAware: Boolean get() = BootReceiver.enabled && (publicStore.getBoolean(Key.directBootAware) ?: false)
|
||||
val directBootAware: Boolean get() = BootReceiver.enabled && publicStore.getBoolean(Key.directBootAware) == true
|
||||
var serviceMode: String
|
||||
get() = publicStore.getString(Key.serviceMode) ?: Key.modeVpn
|
||||
set(value) = publicStore.putString(Key.serviceMode, value)
|
||||
|
||||
@@ -28,11 +28,7 @@ import android.util.AttributeSet
|
||||
class IconListPreference(context: Context, attrs: AttributeSet? = null) : ListPreference(context, attrs) {
|
||||
var entryIcons: Array<Drawable?>? = null
|
||||
val selectedEntry: Int get() = entryValues.indexOf(value)
|
||||
val entryIcon: Drawable? get() = try {
|
||||
entryIcons?.get(selectedEntry)
|
||||
} catch (_: ArrayIndexOutOfBoundsException) {
|
||||
null
|
||||
}
|
||||
val entryIcon: Drawable? get() = entryIcons?.getOrNull(selectedEntry)
|
||||
// fun setEntryIcons(@ArrayRes entryIconsResId: Int) {
|
||||
// val array = getContext().getResources().obtainTypedArray(entryIconsResId)
|
||||
// entryIcons = Array(array.length(), { i -> array.getDrawable(i) })
|
||||
@@ -49,7 +45,7 @@ class IconListPreference(context: Context, attrs: AttributeSet? = null) : ListPr
|
||||
}
|
||||
|
||||
init {
|
||||
super.setOnPreferenceChangeListener({ preference, newValue ->
|
||||
super.setOnPreferenceChangeListener { preference, newValue ->
|
||||
val listener = listener
|
||||
if (listener == null || listener.onPreferenceChange(preference, newValue)) {
|
||||
value = newValue.toString()
|
||||
@@ -57,7 +53,7 @@ class IconListPreference(context: Context, attrs: AttributeSet? = null) : ListPr
|
||||
if (entryIcons != null) icon = entryIcon
|
||||
true
|
||||
} else false
|
||||
})
|
||||
}
|
||||
// val a = context.obtainStyledAttributes(attrs, R.styleable.IconListPreference)
|
||||
// val entryIconsResId: Int = a.getResourceId(R.styleable.IconListPreference_entryIcons, -1)
|
||||
// if (entryIconsResId != -1) entryIcons = entryIconsResId
|
||||
|
||||
@@ -85,12 +85,12 @@ class ConfigActivity : AppCompatActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
try {
|
||||
taskerOption = Settings.fromIntent(intent!!)
|
||||
} catch (_: Exception) {
|
||||
val intent = intent
|
||||
if (intent == null) {
|
||||
finish()
|
||||
return
|
||||
}
|
||||
taskerOption = Settings.fromIntent(intent)
|
||||
setContentView(R.layout.layout_tasker)
|
||||
|
||||
val toolbar = findViewById<Toolbar>(R.id.toolbar)
|
||||
|
||||
@@ -44,8 +44,8 @@ class Settings(bundle: Bundle?) {
|
||||
if (profileId >= 0) bundle.putInt(KEY_PROFILE_ID, profileId)
|
||||
val profile = ProfileManager.getProfile(profileId)
|
||||
return Intent().putExtra(ApiIntent.EXTRA_BUNDLE, bundle).putExtra(ApiIntent.EXTRA_STRING_BLURB,
|
||||
if (profile != null)
|
||||
context.getString(if (switchOn) R.string.start_service else R.string.stop_service, profile.formattedName)
|
||||
if (profile != null) context.getString(if (switchOn) R.string.start_service else R.string.stop_service,
|
||||
profile.formattedName)
|
||||
else context.getString(if (switchOn) R.string.start_service_default else R.string.stop))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,9 +20,7 @@
|
||||
|
||||
package com.github.shadowsocks.utils
|
||||
|
||||
import java.util.ArrayList
|
||||
import java.util.Arrays
|
||||
import java.util.StringTokenizer
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Commandline objects help handling command lines specifying processes to
|
||||
@@ -86,9 +84,7 @@ object Commandline {
|
||||
* @return empty string for null or no command, else every argument split
|
||||
* by spaces and quoted by quoting rules.
|
||||
*/
|
||||
fun toString(args: Array<String>): String {
|
||||
return toString(Arrays.asList(*args)) // thanks to Java, arrays aren't iterable
|
||||
}
|
||||
fun toString(args: Array<String>) = toString(args.asIterable()) // thanks to Java, arrays aren't iterable
|
||||
|
||||
/**
|
||||
* Crack a command line.
|
||||
|
||||
@@ -7,7 +7,7 @@ import com.github.shadowsocks.database.Profile
|
||||
import com.github.shadowsocks.database.ProfileManager
|
||||
import com.github.shadowsocks.preference.DataStore
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.IOException
|
||||
import java.io.ObjectInputStream
|
||||
import java.io.ObjectOutputStream
|
||||
|
||||
@@ -17,7 +17,7 @@ object DirectBoot {
|
||||
|
||||
fun getDeviceProfile(): Profile? = try {
|
||||
ObjectInputStream(file.inputStream()).use { it.readObject() as Profile }
|
||||
} catch (_: FileNotFoundException) { null }
|
||||
} catch (_: IOException) { null }
|
||||
|
||||
fun clean() {
|
||||
file.delete()
|
||||
|
||||
@@ -28,8 +28,8 @@ fun String.parseNumericAddress(): InetAddress? {
|
||||
}
|
||||
|
||||
fun parsePort(str: String?, default: Int, min: Int = 1025): Int {
|
||||
val x = str?.toIntOrNull() ?: default
|
||||
return if (x < min || x > 65535) default else x
|
||||
val value = str?.toIntOrNull() ?: default
|
||||
return if (value < min || value > 65535) default else value
|
||||
}
|
||||
|
||||
fun broadcastReceiver(callback: (Context, Intent) -> Unit): BroadcastReceiver = object : BroadcastReceiver() {
|
||||
@@ -72,6 +72,6 @@ private class SortedListIterable<out T>(private val list: SortedList<T>) : Itera
|
||||
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 = list[count++]
|
||||
override fun next(): T = if (hasNext()) list[count++] else throw NoSuchElementException()
|
||||
}
|
||||
fun <T> SortedList<T>.asIterable(): Iterable<T> = SortedListIterable(this)
|
||||
|
||||
@@ -29,7 +29,6 @@ import android.support.graphics.drawable.AnimatedVectorDrawableCompat
|
||||
import android.support.v7.widget.TooltipCompat
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import com.github.shadowsocks.App.Companion.app
|
||||
import com.github.shadowsocks.R
|
||||
import com.github.shadowsocks.bg.BaseService
|
||||
import java.util.*
|
||||
|
||||
@@ -70,8 +70,8 @@ abstract class NativePluginProvider : ContentProvider() {
|
||||
/**
|
||||
* Returns executable entry absolute path. This is used if plugin is sharing UID with the host.
|
||||
*
|
||||
* Default behavior is throwing UnsupportedOperationException. If you don't wish to use this feature, use the default
|
||||
* behavior.
|
||||
* Default behavior is throwing UnsupportedOperationException. If you don't wish to use this feature, use the
|
||||
* default behavior.
|
||||
*
|
||||
* @return Absolute path for executable entry.
|
||||
*/
|
||||
|
||||
@@ -29,13 +29,11 @@ import android.widget.Toast
|
||||
* Activity that's capable of getting EXTRA_OPTIONS input.
|
||||
*/
|
||||
abstract class OptionsCapableActivity : AppCompatActivity() {
|
||||
protected fun pluginOptions(intent: Intent = this.intent): PluginOptions {
|
||||
return try {
|
||||
PluginOptions(intent.getStringExtra(PluginContract.EXTRA_OPTIONS))
|
||||
} catch (exc: IllegalArgumentException) {
|
||||
Toast.makeText(this, exc.message, Toast.LENGTH_SHORT).show()
|
||||
PluginOptions()
|
||||
}
|
||||
protected fun pluginOptions(intent: Intent = this.intent) = try {
|
||||
PluginOptions(intent.getStringExtra(PluginContract.EXTRA_OPTIONS))
|
||||
} catch (exc: IllegalArgumentException) {
|
||||
Toast.makeText(this, exc.message, Toast.LENGTH_SHORT).show()
|
||||
PluginOptions()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user