@@ -32,10 +32,10 @@ libraryDependencies ++= Seq(
|
||||
"commons-net" % "commons-net" % "3.3",
|
||||
"com.google.zxing" % "android-integration" % "3.2.1",
|
||||
"net.glxn.qrgen" % "android" % "2.0",
|
||||
"com.google.android.gms" % "play-services-base" % "8.1.0",
|
||||
"com.google.android.gms" % "play-services-ads" % "8.1.0",
|
||||
"com.google.android.gms" % "play-services-analytics" % "8.1.0",
|
||||
"com.android.support" % "design" % "23.1.0",
|
||||
"com.google.android.gms" % "play-services-base" % "8.3.0",
|
||||
"com.google.android.gms" % "play-services-ads" % "8.3.0",
|
||||
"com.google.android.gms" % "play-services-analytics" % "8.3.0",
|
||||
"com.android.support" % "design" % "23.1.1",
|
||||
"com.github.jorgecastilloprz" % "fabprogresscircle" % "1.01",
|
||||
"com.j256.ormlite" % "ormlite-core" % "4.48",
|
||||
"com.j256.ormlite" % "ormlite-android" % "4.48"
|
||||
|
||||
@@ -2,39 +2,7 @@
|
||||
|
||||
<resources>
|
||||
|
||||
<style name="Toolbar.Logo" parent="TextAppearance.Widget.AppCompat.Toolbar.Title">
|
||||
<item name="android:textSize">24sp</item>
|
||||
</style>
|
||||
<style name="Theme.Material" parent="Theme.AppCompat.Light.NoActionBar">
|
||||
<item name="android:alertDialogTheme">@style/Theme.Material.Dialog.Alert</item>
|
||||
<item name="android:dialogTheme">@style/Theme.Material.Dialog</item>
|
||||
<item name="android:navigationBarColor">@color/material_accent_700</item>
|
||||
<item name="colorAccent">@color/material_accent_500</item>
|
||||
<item name="colorButtonNormal">@color/material_accent_500</item>
|
||||
<item name="colorPrimary">@color/material_accent_500</item>
|
||||
<item name="colorPrimaryDark">@color/material_accent_700</item>
|
||||
</style>
|
||||
<style name="Theme.Material.Dialog" parent="Theme.AppCompat.Light.Dialog">
|
||||
<item name="colorAccent">@color/material_accent_500</item>
|
||||
<item name="colorPrimary">@color/material_accent_500</item>
|
||||
<item name="colorPrimaryDark">@color/material_accent_700</item>
|
||||
</style>
|
||||
<style name="Theme.Material.Dialog.Alert" parent="Theme.AppCompat.Light.Dialog.Alert">
|
||||
<item name="colorAccent">@color/material_accent_500</item>
|
||||
<item name="colorPrimary">@color/material_accent_500</item>
|
||||
<item name="colorPrimaryDark">@color/material_accent_700</item>
|
||||
</style>
|
||||
|
||||
<style name="PopupTheme" parent="Theme.Material.Dialog">
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
<item name="android:windowFrame">@null</item>
|
||||
<item name="android:windowIsFloating">false</item>
|
||||
<item name="android:windowContentOverlay">@null</item>
|
||||
<item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item>
|
||||
<item name="android:windowSoftInputMode">stateAlwaysHidden</item>
|
||||
<item name="android:windowActionModeOverlay">true</item>
|
||||
<item name="android:colorBackgroundCacheHint">@null</item>
|
||||
<item name="android:windowIsTranslucent">true</item>
|
||||
</style>
|
||||
<style name="Theme.Material.Dialog" parent="Theme.Material.DialogBase"/>
|
||||
<style name="Theme.Material.Dialog.Alert" parent="Theme.Material.Dialog.AlertBase"/>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -74,6 +74,10 @@
|
||||
|
||||
<string name="stat">网络流量</string>
|
||||
<string name="stat_summary">发送:\t%1$s\t|\t接收:\t%2$s\n上传:\t%3$s\t|\t下载:\t%4$s</string>
|
||||
<string name="stat_profiles">\n发送:\t%1$s\t|\t接收:\t%2$s</string>
|
||||
<plurals name="bytes">
|
||||
<item quantity="other">字节</item>
|
||||
</plurals>
|
||||
|
||||
<!-- profile -->
|
||||
<string name="add_profile_dialog">为影梭添加此配置文件?</string>
|
||||
|
||||
@@ -13,7 +13,12 @@
|
||||
<string name="nat_summary">Use NAT mode instead of VPN mode</string>
|
||||
<string name="nat_summary_no_root">NAT mode is disabled without root</string>
|
||||
<string name="stat">Network Traffic</string>
|
||||
<string name="stat_summary">Send:\t%1$s\t|\tReceive:\t%2$s\nUpload:\t%3$s\t|\tDownload:\t%4$s</string>
|
||||
<string name="stat_summary">Sent: \t%1$s\t|\tReceived: \t%2$s\nUpload: \t%3$s\t|\tDownload: \t%4$s</string>
|
||||
<string name="stat_profiles">\nSent: \t%1$s\t|\tReceived: \t%2$s</string>
|
||||
<plurals name="bytes">
|
||||
<item quantity="one">Byte</item>
|
||||
<item quantity="other">Bytes</item>
|
||||
</plurals>
|
||||
|
||||
<!-- proxy category -->
|
||||
<string name="proxy_cat">Server Settings</string>
|
||||
|
||||
@@ -14,16 +14,20 @@
|
||||
<item name="colorPrimary">@color/material_accent_500</item>
|
||||
<item name="colorPrimaryDark">@color/material_accent_700</item>
|
||||
</style>
|
||||
<style name="Theme.Material.Dialog" parent="Theme.AppCompat.Light.Dialog">
|
||||
<style name="Theme.Material.DialogBase" parent="Theme.AppCompat.Light.Dialog">
|
||||
<item name="colorAccent">@color/material_accent_500</item>
|
||||
<item name="colorPrimary">@color/material_accent_500</item>
|
||||
<item name="colorPrimaryDark">@color/material_accent_700</item>
|
||||
</style>
|
||||
<style name="Theme.Material.Dialog.AlertBase" parent="Theme.AppCompat.Light.Dialog.Alert">
|
||||
<item name="colorAccent">@color/material_accent_500</item>
|
||||
<item name="colorPrimary">@color/material_accent_500</item>
|
||||
<item name="colorPrimaryDark">@color/material_accent_700</item>
|
||||
</style>
|
||||
<style name="Theme.Material.Dialog" parent="Theme.Material.DialogBase">
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
</style>
|
||||
<style name="Theme.Material.Dialog.Alert" parent="Theme.AppCompat.Light.Dialog.Alert">
|
||||
<item name="colorAccent">@color/material_accent_500</item>
|
||||
<item name="colorPrimary">@color/material_accent_500</item>
|
||||
<item name="colorPrimaryDark">@color/material_accent_700</item>
|
||||
<style name="Theme.Material.Dialog.Alert" parent="Theme.Material.Dialog.AlertBase">
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
</style>
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ class ParserActivity extends Activity {
|
||||
.setTitle(R.string.add_profile_dialog)
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(android.R.string.yes, ((dialog: DialogInterface, id: Int) => {
|
||||
ShadowsocksApplication.profileManager.createOrUpdateProfile(profile.get)
|
||||
ShadowsocksApplication.profileManager.createProfile(profile.get)
|
||||
dialog.dismiss()
|
||||
}): DialogInterface.OnClickListener)
|
||||
.setNegativeButton(android.R.string.no, ((dialog: DialogInterface, id: Int) => {
|
||||
|
||||
@@ -10,11 +10,13 @@ import android.support.v7.widget.Toolbar.OnMenuItemClickListener
|
||||
import android.support.v7.widget.helper.ItemTouchHelper
|
||||
import android.support.v7.widget.helper.ItemTouchHelper.SimpleCallback
|
||||
import android.support.v7.widget.{DefaultItemAnimator, LinearLayoutManager, RecyclerView, Toolbar}
|
||||
import android.text.style.TextAppearanceSpan
|
||||
import android.text.{Spanned, SpannableStringBuilder}
|
||||
import android.view.View.{OnAttachStateChangeListener, OnClickListener}
|
||||
import android.view.{LayoutInflater, MenuItem, View, ViewGroup}
|
||||
import android.widget.{LinearLayout, ImageView, CheckedTextView, Toast}
|
||||
import com.github.shadowsocks.database.Profile
|
||||
import com.github.shadowsocks.utils.{Parser, Utils}
|
||||
import com.github.shadowsocks.utils.{TrafficMonitor, Parser, Utils}
|
||||
import com.google.zxing.integration.android.IntentIntegrator
|
||||
import net.glxn.qrgen.android.QRCode
|
||||
|
||||
@@ -54,10 +56,23 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe
|
||||
})
|
||||
}
|
||||
|
||||
def updateText() {
|
||||
val builder = new SpannableStringBuilder
|
||||
builder.append(item.name)
|
||||
if (item.tx != 0 || item.rx != 0) {
|
||||
val start = builder.length
|
||||
builder.append(getString(R.string.stat_profiles,
|
||||
TrafficMonitor.formatTraffic(item.tx), TrafficMonitor.formatTraffic(item.rx)))
|
||||
builder.setSpan(new TextAppearanceSpan(ProfileManagerActivity.this, android.R.style.TextAppearance_Small),
|
||||
start + 1, builder.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
}
|
||||
text.setText(builder)
|
||||
}
|
||||
|
||||
def bind(item: Profile) {
|
||||
text.setText(item.name)
|
||||
text.setChecked(item.id == ShadowsocksApplication.profileId)
|
||||
this.item = item
|
||||
updateText()
|
||||
text.setChecked(item.id == ShadowsocksApplication.profileId)
|
||||
}
|
||||
|
||||
def onClick(v: View) = {
|
||||
@@ -153,7 +168,7 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe
|
||||
override def onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
|
||||
val scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, data)
|
||||
if (scanResult != null) Parser.parse(scanResult.getContents) match {
|
||||
case Some(profile) => ShadowsocksApplication.profileManager.createOrUpdateProfile(profile)
|
||||
case Some(profile) => ShadowsocksApplication.profileManager.createProfile(profile)
|
||||
case _ => // ignore
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,7 +138,6 @@ object Shadowsocks {
|
||||
case Key.isUdpDns => updateSwitchPreference(pref, profile.udpdns)
|
||||
case Key.isAuth => updateSwitchPreference(pref, profile.auth)
|
||||
case Key.isIpv6 => updateSwitchPreference(pref, profile.ipv6)
|
||||
case _ =>
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -476,17 +475,15 @@ class Shadowsocks
|
||||
} else {
|
||||
TrafficMonitor.reset()
|
||||
}
|
||||
val pm = getSystemService(Context.POWER_SERVICE).asInstanceOf[PowerManager]
|
||||
if (pm.isScreenOn) {
|
||||
val trafficStat = getString(R.string.stat_summary).formatLocal(Locale.ENGLISH,
|
||||
TrafficMonitor.getTxRate, TrafficMonitor.getRxRate,
|
||||
TrafficMonitor.getTxTotal, TrafficMonitor.getRxTotal)
|
||||
handler.post(() => {
|
||||
preferences.findPreference(Key.stat).setSummary(trafficStat)
|
||||
})
|
||||
}
|
||||
val trafficStat = getString(R.string.stat_summary).formatLocal(Locale.ENGLISH,
|
||||
TrafficMonitor.getTxRate, TrafficMonitor.getRxRate,
|
||||
TrafficMonitor.getTxTotal, TrafficMonitor.getRxTotal)
|
||||
handler.post(() => {
|
||||
preferences.findPreference(Key.stat).setSummary(trafficStat)
|
||||
})
|
||||
}
|
||||
}
|
||||
task.run()
|
||||
timer = new Timer(true)
|
||||
timer.schedule(task, TIMER_INTERVAL * 1000, TIMER_INTERVAL * 1000)
|
||||
}
|
||||
|
||||
@@ -65,35 +65,36 @@ class DBHelper(val context: Context)
|
||||
if (oldVersion < 7) {
|
||||
profileDao.executeRawNoArgs("DROP TABLE IF EXISTS 'profile';")
|
||||
onCreate(database, connectionSource)
|
||||
} else {
|
||||
if (oldVersion < 8) {
|
||||
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN udpdns SMALLINT;")
|
||||
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN route VARCHAR;")
|
||||
}
|
||||
if (oldVersion < 9) {
|
||||
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN route VARCHAR;")
|
||||
}
|
||||
if (oldVersion < 10) {
|
||||
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN auth SMALLINT;")
|
||||
}
|
||||
if (oldVersion < 11) {
|
||||
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN ipv6 SMALLINT;")
|
||||
}
|
||||
if (oldVersion < 12) {
|
||||
profileDao.executeRawNoArgs("ALTER TABLE `profile` RENAME TO `tmp`;")
|
||||
onCreate(database, connectionSource)
|
||||
profileDao.executeRawNoArgs(
|
||||
"INSERT INTO `profile`(id, name, host, localPort, remotePort, password, method, route, proxyApps, bypass," +
|
||||
" udpdns, auth, ipv6, individual) " +
|
||||
"SELECT id, name, host, localPort, remotePort, password, method, route, 1 - global, bypass, udpdns, auth," +
|
||||
" ipv6, individual FROM `tmp`;")
|
||||
profileDao.executeRawNoArgs("DROP TABLE `tmp`;")
|
||||
}
|
||||
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 DATE;")
|
||||
}
|
||||
return
|
||||
}
|
||||
if (oldVersion < 8) {
|
||||
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN udpdns SMALLINT;")
|
||||
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN route VARCHAR;")
|
||||
}
|
||||
if (oldVersion < 9) {
|
||||
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN route VARCHAR;")
|
||||
}
|
||||
if (oldVersion < 10) {
|
||||
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN auth SMALLINT;")
|
||||
}
|
||||
if (oldVersion < 11) {
|
||||
profileDao.executeRawNoArgs("ALTER TABLE `profile` ADD COLUMN ipv6 SMALLINT;")
|
||||
}
|
||||
if (oldVersion < 12) {
|
||||
profileDao.executeRawNoArgs("ALTER TABLE `profile` RENAME TO `tmp`;")
|
||||
onCreate(database, connectionSource)
|
||||
profileDao.executeRawNoArgs(
|
||||
"INSERT INTO `profile`(id, name, host, localPort, remotePort, password, method, route, proxyApps, bypass," +
|
||||
" udpdns, auth, ipv6, individual) " +
|
||||
"SELECT id, name, host, localPort, remotePort, password, method, route, 1 - global, bypass, udpdns, auth," +
|
||||
" ipv6, individual FROM `tmp`;")
|
||||
profileDao.executeRawNoArgs("DROP TABLE `tmp`;")
|
||||
return
|
||||
}
|
||||
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 DATE;")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,15 +49,27 @@ class ProfileManager(settings: SharedPreferences, dbHelper: DBHelper) {
|
||||
var profileAddedListener: Profile => Any = _
|
||||
def setProfileAddedListener(listener: Profile => Any) = this.profileAddedListener = listener
|
||||
|
||||
def createOrUpdateProfile(profile: Profile): Boolean = {
|
||||
def createProfile(p: Profile = null): Profile = {
|
||||
try {
|
||||
val profile = if (p == null) new Profile else p
|
||||
profile.id = 0
|
||||
ShadowsocksApplication.currentProfile match {
|
||||
case Some(oldProfile) =>
|
||||
// Copy Feature Settings from old profile
|
||||
profile.route = oldProfile.route
|
||||
profile.ipv6 = oldProfile.ipv6
|
||||
profile.proxyApps = oldProfile.proxyApps
|
||||
profile.individual = oldProfile.individual
|
||||
profile.udpdns = oldProfile.udpdns
|
||||
case _ =>
|
||||
}
|
||||
dbHelper.profileDao.createOrUpdate(profile)
|
||||
if (profileAddedListener != null) profileAddedListener(profile)
|
||||
true
|
||||
profile
|
||||
} catch {
|
||||
case ex: Exception =>
|
||||
Log.e(Shadowsocks.TAG, "addProfile", ex)
|
||||
false
|
||||
p
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,11 +126,7 @@ class ProfileManager(settings: SharedPreferences, dbHelper: DBHelper) {
|
||||
|
||||
def load(id: Int): Profile = {
|
||||
|
||||
val profile = getProfile(id) getOrElse {
|
||||
val p = new Profile()
|
||||
createOrUpdateProfile(p)
|
||||
p
|
||||
}
|
||||
val profile = getProfile(id) getOrElse createProfile()
|
||||
|
||||
val edit = settings.edit()
|
||||
edit.putBoolean(Key.isProxyApps, profile.proxyApps)
|
||||
@@ -175,7 +183,7 @@ class ProfileManager(settings: SharedPreferences, dbHelper: DBHelper) {
|
||||
remotePort = 443
|
||||
password = "u1rRWTssNv0p"
|
||||
}
|
||||
createOrUpdateProfile(profile)
|
||||
createProfile(profile)
|
||||
profile
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package com.github.shadowsocks.utils
|
||||
|
||||
import java.lang.{Math, System}
|
||||
import java.text.DecimalFormat
|
||||
|
||||
import android.net.TrafficStats
|
||||
import android.os.Process
|
||||
import java.lang.{String, System, Math}
|
||||
import java.util.Locale
|
||||
import com.github.shadowsocks.{R, ShadowsocksApplication}
|
||||
|
||||
case class Traffic(tx: Long, rx: Long, timestamp: Long)
|
||||
|
||||
@@ -19,31 +21,33 @@ object TrafficMonitor {
|
||||
var txTotal: Long = 0
|
||||
var rxTotal: Long = 0
|
||||
|
||||
def getTraffic(): Traffic = {
|
||||
def getTraffic: Traffic = {
|
||||
new Traffic(Math.max(TrafficStats.getTotalTxBytes - TrafficStats.getUidTxBytes(uid), 0),
|
||||
Math.max(TrafficStats.getTotalRxBytes - TrafficStats.getUidRxBytes(uid), 0), System.currentTimeMillis())
|
||||
}
|
||||
|
||||
def formatTraffic(n: Long): String = {
|
||||
if (n <= 1024) {
|
||||
"%d KB".formatLocal(Locale.ENGLISH, n)
|
||||
} else if (n > 1024) {
|
||||
"%d MB".formatLocal(Locale.ENGLISH, n / 1024)
|
||||
} else if (n > 1024 * 1024) {
|
||||
"%d GB".formatLocal(Locale.ENGLISH, n / 1024 / 1024)
|
||||
} else if (n > 1024 * 1024 * 1024) {
|
||||
"%d TB".formatLocal(Locale.ENGLISH, n / 1024 / 1024 / 1024)
|
||||
} else {
|
||||
">1024 TB"
|
||||
private val units = Array("KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB", "BB", "NB", "DB", "CB")
|
||||
private val numberFormat = new DecimalFormat("0.00")
|
||||
def formatTraffic(size: Long): String = {
|
||||
var n: Double = size
|
||||
var i = -1
|
||||
while (n >= 1024) {
|
||||
n /= 1024
|
||||
i = i + 1
|
||||
}
|
||||
if (i < 0) size + " " + ShadowsocksApplication.instance.getResources.getQuantityString(R.plurals.bytes, size.toInt)
|
||||
else numberFormat.format(n) + ' ' + units(i)
|
||||
}
|
||||
|
||||
def update() {
|
||||
val now = getTraffic
|
||||
txRate = (now.tx - last.tx) / (now.timestamp - last.timestamp)
|
||||
rxRate = (now.rx - last.rx) / (now.timestamp - last.timestamp)
|
||||
txTotal += (now.tx - last.tx) / 1024
|
||||
rxTotal += (now.rx - last.rx) / 1024
|
||||
val delta = now.timestamp - last.timestamp
|
||||
if (delta != 0) {
|
||||
txRate = (now.tx - last.tx) / delta
|
||||
rxRate = (now.rx - last.rx) / delta
|
||||
}
|
||||
txTotal += now.tx - last.tx
|
||||
rxTotal += now.rx - last.rx
|
||||
last = now
|
||||
}
|
||||
|
||||
@@ -80,4 +84,3 @@ object TrafficMonitor {
|
||||
}
|
||||
}
|
||||
|
||||
class TrafficMonitor ()
|
||||
|
||||
Reference in New Issue
Block a user