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