diff --git a/build.sbt b/build.sbt
index 2681f52e..22db1b05 100644
--- a/build.sbt
+++ b/build.sbt
@@ -4,7 +4,8 @@ libraryDependencies ++= Seq(
"com.google.android" % "admob" % "6.4.1",
"dnsjava" % "dnsjava" % "2.1.5",
"org.scalaj" %% "scalaj-http" % "0.3.10",
- "commons-net" % "commons-net" % "3.3"
+ "commons-net" % "commons-net" % "3.3",
+ "com.google.zxing" % "android-integration" % "2.2"
)
libraryDependencies ++= Seq(
diff --git a/src/main/res/values-zh/strings.xml b/src/main/res/values-zh/strings.xml
index 3d597411..957104f5 100755
--- a/src/main/res/values-zh/strings.xml
+++ b/src/main/res/values-zh/strings.xml
@@ -66,4 +66,11 @@
刷新 DNS 缓存
刷新中...
添加配置文件
+
+
+
+ - 扫描二维码
+ - 手动设置
+
+
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index 618c6d6d..ab72d692 100755
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -83,4 +83,10 @@
Flushing
Add Profile
+
+
+ - Scan QR code
+ - Manually Setting
+
+
diff --git a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala
index 2c373356..b98678d2 100644
--- a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala
+++ b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala
@@ -72,6 +72,7 @@ import com.github.shadowsocks.database.Category
import com.github.shadowsocks.utils._
import com.github.shadowsocks.database.Item
import com.github.shadowsocks.database.Category
+import com.google.zxing.integration.android.IntentIntegrator
class ProfileIconDownloader(context: Context, connectTimeout: Int, readTimeout: Int)
extends BaseImageDownloader(context, connectTimeout, readTimeout) {
@@ -569,6 +570,50 @@ class Shadowsocks
drawer.setActiveView(v, pos)
}
+
+ def newProfile(id: Int) {
+
+ val builder = new AlertDialog.Builder(this)
+ builder.setTitle(R.string.add_profile)
+ .setItems(R.array.add_profile_methods, new DialogInterface.OnClickListener() {
+ def onClick(dialog: DialogInterface, which: Int) {
+ which match {
+ case 0 => {
+ val integrator = new IntentIntegrator(Shadowsocks.this)
+ integrator.initiateScan()
+ }
+ case 1 => addProfile(id)
+ case _ =>
+ }
+ }
+ })
+ builder.create().show()
+
+ }
+
+ def addProfile(profile: Profile) {
+ drawer.closeMenu(true)
+
+ val h = showProgress(getString(R.string.loading))
+
+ handler.postDelayed(new Runnable {
+ def run() {
+ currentProfile = profile
+ profileManager.createOrUpdateProfile(currentProfile)
+ profileManager.reload(currentProfile.id)
+ menuAdapter.updateList(getMenuList, currentProfile.id)
+
+ if (!isSinglePane) {
+ sendBroadcast(new Intent(Action.UPDATE_FRAGMENT))
+ } else {
+ updatePreferenceScreen()
+ }
+
+ h.sendEmptyMessage(0)
+ }
+ }, 600)
+ }
+
def addProfile(id: Int) {
drawer.closeMenu(true)
@@ -665,7 +710,7 @@ class Shadowsocks
buf ++= getProfileList
buf +=
- new Item(-400, getString(R.string.add_profile), android.R.drawable.ic_menu_add, addProfile)
+ new Item(-400, getString(R.string.add_profile), android.R.drawable.ic_menu_add, newProfile)
buf += new Category(getString(R.string.settings))
@@ -827,16 +872,24 @@ class Shadowsocks
}
override def onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
- resultCode match {
- case Activity.RESULT_OK => {
- prepared = true
- if (!serviceStart) {
- switchButton.setChecked(false)
- }
+ val scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, data)
+ if (scanResult != null) {
+ ShadowParser.parse(scanResult.getContents) match {
+ case Some(profile) => addProfile(profile)
+ case _ => // ignore
}
- case _ => {
- clearDialog()
- Log.e(Shadowsocks.TAG, "Failed to start VpnService")
+ } else {
+ resultCode match {
+ case Activity.RESULT_OK => {
+ prepared = true
+ if (!serviceStart) {
+ switchButton.setChecked(false)
+ }
+ }
+ case _ => {
+ clearDialog()
+ Log.e(Shadowsocks.TAG, "Failed to start VpnService")
+ }
}
}
}
diff --git a/src/main/scala/com/github/shadowsocks/utils/ShadowParser.scala b/src/main/scala/com/github/shadowsocks/utils/ShadowParser.scala
new file mode 100644
index 00000000..338a9705
--- /dev/null
+++ b/src/main/scala/com/github/shadowsocks/utils/ShadowParser.scala
@@ -0,0 +1,34 @@
+package com.github.shadowsocks.utils
+
+import com.github.shadowsocks.database.Profile
+import android.net.Uri
+import android.util.{Log, Base64}
+
+object ShadowParser {
+ val TAG = "ShadowParser"
+ def parse (data: String): Option[Profile] = {
+ try {
+ Log.d(TAG, data)
+ val uri = Uri.parse(data.trim)
+ if (uri.isOpaque && uri.getScheme == "shadow") {
+ val encoded = data.replace("shadow:", "")
+ val content = new String(Base64.decode(encoded, Base64.NO_PADDING), "UTF-8")
+ val info = content.split('@')
+ val encinfo = info(0).split(':')
+ val serverinfo = info(1).split(':')
+
+ val profile = new Profile
+ profile.name = serverinfo(0)
+ profile.host = serverinfo(0)
+ profile.remotePort = serverinfo(1).toInt
+ profile.localPort = 1080
+ profile.method = encinfo(0)
+ profile.password = encinfo(1)
+ return Some(profile)
+ }
+ } catch {
+ case ex : Exception => Log.e(TAG, "parser error", ex)// Ignore
+ }
+ None
+ }
+}