diff --git a/build.sbt b/build.sbt index 45c762f1..5a83ff5c 100644 --- a/build.sbt +++ b/build.sbt @@ -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" diff --git a/src/main/res/values-v21/styles.xml b/src/main/res/values-v21/styles.xml index efa07058..3d6dc203 100644 --- a/src/main/res/values-v21/styles.xml +++ b/src/main/res/values-v21/styles.xml @@ -2,39 +2,7 @@ - - - - - - + - + + - diff --git a/src/main/scala/com/github/shadowsocks/ParserActivity.scala b/src/main/scala/com/github/shadowsocks/ParserActivity.scala index 6d24af45..f1265ccd 100644 --- a/src/main/scala/com/github/shadowsocks/ParserActivity.scala +++ b/src/main/scala/com/github/shadowsocks/ParserActivity.scala @@ -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) => { diff --git a/src/main/scala/com/github/shadowsocks/ProfileManagerActivity.scala b/src/main/scala/com/github/shadowsocks/ProfileManagerActivity.scala index 8bbebe0c..ce69db3c 100644 --- a/src/main/scala/com/github/shadowsocks/ProfileManagerActivity.scala +++ b/src/main/scala/com/github/shadowsocks/ProfileManagerActivity.scala @@ -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 } } diff --git a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala index 3ce10ab7..a93a2813 100644 --- a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala +++ b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala @@ -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) } diff --git a/src/main/scala/com/github/shadowsocks/database/DBHelper.scala b/src/main/scala/com/github/shadowsocks/database/DBHelper.scala index 627d6b48..3df2e5d5 100644 --- a/src/main/scala/com/github/shadowsocks/database/DBHelper.scala +++ b/src/main/scala/com/github/shadowsocks/database/DBHelper.scala @@ -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;") } } } diff --git a/src/main/scala/com/github/shadowsocks/database/ProfileManager.scala b/src/main/scala/com/github/shadowsocks/database/ProfileManager.scala index fd2cda35..b83fb597 100644 --- a/src/main/scala/com/github/shadowsocks/database/ProfileManager.scala +++ b/src/main/scala/com/github/shadowsocks/database/ProfileManager.scala @@ -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 } } diff --git a/src/main/scala/com/github/shadowsocks/utils/TrafficMonitor.scala b/src/main/scala/com/github/shadowsocks/utils/TrafficMonitor.scala index b762eb2e..3236a026 100644 --- a/src/main/scala/com/github/shadowsocks/utils/TrafficMonitor.scala +++ b/src/main/scala/com/github/shadowsocks/utils/TrafficMonitor.scala @@ -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 ()