diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml
index 1a347e07..4a41d8a0 100644
--- a/src/main/AndroidManifest.xml
+++ b/src/main/AndroidManifest.xml
@@ -109,6 +109,14 @@
+
+
+
+
+
+
+
+
diff --git a/src/main/scala/com/github/shadowsocks/AppManager.scala b/src/main/scala/com/github/shadowsocks/AppManager.scala
index 097a491a..d08e8acb 100644
--- a/src/main/scala/com/github/shadowsocks/AppManager.scala
+++ b/src/main/scala/com/github/shadowsocks/AppManager.scala
@@ -39,7 +39,8 @@
package com.github.shadowsocks
-import android.content.{ClipData, ClipboardManager, Context, SharedPreferences}
+import android.content.pm.PackageManager
+import android.content.{ClipData, ClipboardManager, Context}
import android.graphics.PixelFormat
import android.graphics.drawable.Drawable
import android.os.{Bundle, Handler}
@@ -61,14 +62,24 @@ import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import scala.language.implicitConversions
-case class ProxiedApp(uid: Int, name: String, packageName: String, icon: Drawable, var proxied: Boolean)
+object AppManager {
+ case class ProxiedApp(uid: Int, name: String, packageName: String, icon: Drawable)
+ private case class ListEntry(switch: Switch, text: TextView, icon: ImageView)
-case class ListEntry(switch: Switch, text: TextView, icon: ImageView)
+ var cachedApps: Array[ProxiedApp] = _
+ private def getApps(pm: PackageManager) = {
+ if (cachedApps == null) cachedApps = pm.getInstalledApplications(0).filter(_.uid >= 10000)
+ .map(a => new ProxiedApp(a.uid, pm.getApplicationLabel(a).toString, a.packageName, a.loadIcon(pm))).toArray
+ cachedApps
+ }
+}
class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnClickListener
with OnMenuItemClickListener {
+ import AppManager._
- private var apps: mutable.Buffer[ProxiedApp] = _
+ private var apps: Array[ProxiedApp] = _
+ private var proxiedApps: mutable.HashSet[Int] = _
private var toolbar: Toolbar = _
private var appListView: ListView = _
private var loadingView: View = _
@@ -78,23 +89,10 @@ class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnC
def loadApps() {
appsLoading = true
- val proxiedApps = ShadowsocksApplication.settings.getString(Key.proxied, "").split('|').toSet
-
- val packageManager = getPackageManager
- apps = packageManager.getInstalledApplications(0).filter(_.uid >= 10000).map {
- case a =>
- val uid = a.uid
- val userName = uid.toString
- val name = packageManager.getApplicationLabel(a).toString
- val packageName = a.packageName
- val proxied = proxiedApps.contains(userName)
- new ProxiedApp(uid, name, packageName, a.loadIcon(packageManager), proxied)
- }.sortWith((a, b) => {
- if (a == null || b == null || a.name == null || b.name == null) {
- true
- } else if (a.proxied == b.proxied) {
- a.name < b.name
- } else a.proxied
+ proxiedApps = ShadowsocksApplication.settings.getString(Key.proxied, "").split('|').map(_.toInt).to[mutable.HashSet]
+ apps = getApps(getPackageManager).sortWith((a, b) => {
+ val aProxied = proxiedApps.contains(a.uid)
+ if (aProxied ^ proxiedApps.contains(b.uid)) aProxied else a.name < b.name
})
adapter = new ArrayAdapter[ProxiedApp](this, R.layout.layout_apps_item, R.id.itemtext, apps) {
override def getView(position: Int, view: View, parent: ViewGroup): View = {
@@ -102,10 +100,9 @@ class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnC
var entry: ListEntry = null
if (convertView == null) {
convertView = getLayoutInflater.inflate(R.layout.layout_apps_item, parent, false)
- val icon = convertView.findViewById(R.id.itemicon).asInstanceOf[ImageView]
- val switch = convertView.findViewById(R.id.itemcheck).asInstanceOf[Switch]
- val text = convertView.findViewById(R.id.itemtext).asInstanceOf[TextView]
- entry = new ListEntry(switch, text, icon)
+ entry = new ListEntry(convertView.findViewById(R.id.itemcheck).asInstanceOf[Switch],
+ convertView.findViewById(R.id.itemtext).asInstanceOf[TextView],
+ convertView.findViewById(R.id.itemicon).asInstanceOf[ImageView])
convertView.setOnClickListener(AppManager.this)
convertView.setTag(entry)
entry.switch.setOnCheckedChangeListener(AppManager.this)
@@ -119,19 +116,19 @@ class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnC
entry.icon.setImageDrawable(app.icon)
val switch = entry.switch
switch.setTag(app)
- switch.setChecked(app.proxied)
+ switch.setChecked(proxiedApps.contains(app.uid))
entry.text.setTag(switch)
convertView
}
}
}
+ private def setProxied(uid: Int, proxied: Boolean) = if (proxied) proxiedApps.add(uid) else proxiedApps.remove(uid)
+
/** Called an application is check/unchecked */
def onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) {
val app: ProxiedApp = buttonView.getTag.asInstanceOf[ProxiedApp]
- if (app != null) {
- app.proxied = isChecked
- }
+ if (app != null) setProxied(app.uid, isChecked)
saveAppSettings(this)
}
@@ -139,8 +136,9 @@ class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnC
val switch = v.getTag.asInstanceOf[ListEntry].switch
val app: ProxiedApp = switch.getTag.asInstanceOf[ProxiedApp]
if (app != null) {
- app.proxied = !app.proxied
- switch.setChecked(app.proxied)
+ val proxied = !proxiedApps.contains(app.uid)
+ setProxied(app.uid, proxied)
+ switch.setChecked(proxied)
}
saveAppSettings(this)
}
@@ -235,12 +233,8 @@ class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnC
def onScroll(view: AbsListView, firstVisibleItem: Int, visibleItemCount: Int,
totalItemCount: Int) {
if (visible) {
- val name: String = apps(firstVisibleItem).name
- if (name != null && name.length > 1) {
- overlay.setText(apps(firstVisibleItem).name.substring(0, 1))
- } else {
- overlay.setText("*")
- }
+ val name = apps(firstVisibleItem).name
+ overlay.setText(if (name != null && name.length > 1) name(0).toString else "*")
overlay.setVisibility(View.VISIBLE)
}
}
@@ -266,16 +260,7 @@ class AppManager extends AppCompatActivity with OnCheckedChangeListener with OnC
}
def saveAppSettings(context: Context) {
- if (apps == null) return
- val proxiedApps = new StringBuilder
- apps.foreach(app =>
- if (app.proxied) {
- proxiedApps ++= app.uid.toString
- proxiedApps += '|'
- })
- val edit: SharedPreferences.Editor = ShadowsocksApplication.settings.edit
- edit.putString(Key.proxied, proxiedApps.toString())
- edit.apply
+ if (!appsLoading) ShadowsocksApplication.settings.edit.putString(Key.proxied, proxiedApps.mkString("|")).apply
}
var handler: Handler = null
diff --git a/src/main/scala/com/github/shadowsocks/AppManagerReceiver.scala b/src/main/scala/com/github/shadowsocks/AppManagerReceiver.scala
new file mode 100644
index 00000000..f754f11a
--- /dev/null
+++ b/src/main/scala/com/github/shadowsocks/AppManagerReceiver.scala
@@ -0,0 +1,11 @@
+package com.github.shadowsocks
+
+import android.content.{Intent, Context, BroadcastReceiver}
+
+/**
+ * @author Mygod
+ */
+class AppManagerReceiver extends BroadcastReceiver {
+ override def onReceive(context: Context, intent: Intent) = if (intent.getAction != Intent.ACTION_PACKAGE_REMOVED ||
+ !intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) AppManager.cachedApps = null
+}
diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksReceiver.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksReceiver.scala
index 5daa69ad..16bd4cd0 100644
--- a/src/main/scala/com/github/shadowsocks/ShadowsocksReceiver.scala
+++ b/src/main/scala/com/github/shadowsocks/ShadowsocksReceiver.scala
@@ -43,9 +43,6 @@ import android.content.{BroadcastReceiver, Context, Intent}
import com.github.shadowsocks.utils._
class ShadowsocksReceiver extends BroadcastReceiver {
-
- val TAG = "Shadowsocks"
-
def onReceive(context: Context, intent: Intent) {
if (ShadowsocksApplication.settings.getBoolean(Key.isAutoConnect, false)) {
Utils.startSsService(context)
diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala
index 966d5440..3140baaf 100644
--- a/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala
+++ b/src/main/scala/com/github/shadowsocks/ShadowsocksVpnService.scala
@@ -63,16 +63,15 @@ class ShadowsocksVpnService extends VpnService with BaseService {
val VPN_MTU = 1500
val PRIVATE_VLAN = "26.26.26.%s"
val PRIVATE_VLAN6 = "fdfe:dcba:9876::%s"
- var conn: ParcelFileDescriptor = null
- var apps: Array[ProxiedApp] = null
- var vpnThread: ShadowsocksVpnThread = null
+ var conn: ParcelFileDescriptor = _
+ var vpnThread: ShadowsocksVpnThread = _
private var notification: ShadowsocksNotification = _
- var closeReceiver: BroadcastReceiver = null
+ var closeReceiver: BroadcastReceiver = _
- var sslocalProcess: Process = null
- var sstunnelProcess: Process = null
- var pdnsdProcess: Process = null
- var tun2socksProcess: Process = null
+ var sslocalProcess: Process = _
+ var sstunnelProcess: Process = _
+ var pdnsdProcess: Process = _
+ var tun2socksProcess: Process = _
def isByass(net: SubnetUtils): Boolean = {
val info = net.getInfo