diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml
index 82c629f0..26e4a77e 100644
--- a/src/main/AndroidManifest.xml
+++ b/src/main/AndroidManifest.xml
@@ -6,11 +6,14 @@
+
+
+
@@ -46,13 +49,20 @@
+ android:excludeFromRecents="true"
+ android:taskAffinity=""
+ android:launchMode="singleInstance">
+
+
+
+
+
-
+ android:contentDescription="@string/share"/>
diff --git a/src/main/res/values-zh/strings.xml b/src/main/res/values-zh/strings.xml
index 01274188..e7dd2411 100755
--- a/src/main/res/values-zh/strings.xml
+++ b/src/main/res/values-zh/strings.xml
@@ -60,7 +60,8 @@
只需在 NAT 模式下使用,需要 ROOT 权限
在没有 ROOT 权限的情况下无法刷新 DNS 缓存
刷新中……
- 二维码
+ 分享
+ 扫描二维码或者使用 NFC(Android Beam)
添加配置文件
NAT 模式 (仅限调试)
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index 57d5a710..9f69e76c 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -91,7 +91,8 @@
Reset all the background services
About
Shadowsocks %s
- QR Code
+ Share
+ Scan QRCode or Use NFC (Android Beam)
Flush DNS cache
Only useful in NAT mode
Only useful in NAT mode. Requires ROOT permission.
diff --git a/src/main/scala/com/github/shadowsocks/ParserActivity.scala b/src/main/scala/com/github/shadowsocks/ParserActivity.scala
index 009cb0d7..25676610 100644
--- a/src/main/scala/com/github/shadowsocks/ParserActivity.scala
+++ b/src/main/scala/com/github/shadowsocks/ParserActivity.scala
@@ -39,10 +39,11 @@
package com.github.shadowsocks
-import android.content.DialogInterface
+import android.content.{Intent, DialogInterface}
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
-import android.os.Bundle
+import android.nfc.{NdefMessage, NfcAdapter}
+import android.os.{Parcelable, Bundle}
import android.support.v7.app.{AppCompatActivity, AlertDialog}
import android.view.WindowManager
import com.github.shadowsocks.utils.Parser
@@ -50,8 +51,24 @@ import com.github.shadowsocks.utils.Parser
class ParserActivity extends AppCompatActivity {
override def onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
- val profiles = Parser.findAll(getIntent.getData.toString)
- if (profiles.isEmpty) { // ignore
+ }
+
+
+ override def onResume() {
+ super.onResume()
+ var sharedStr = ""
+ if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent.getAction)) {
+ val rawMsgs = getIntent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)
+ if (rawMsgs != null) {
+ val msg = rawMsgs(0).asInstanceOf[NdefMessage]
+ val records = msg.getRecords
+ sharedStr = new String(records(0).getPayload)
+ }
+ } else if (Intent.ACTION_VIEW.equals(getIntent.getAction)) {
+ sharedStr = getIntent.getData.toString
+ }
+ val profiles = Parser.findAll(sharedStr)
+ if (profiles.isEmpty) {
finish()
return
}
@@ -62,9 +79,9 @@ class ParserActivity extends AppCompatActivity {
.setPositiveButton(android.R.string.yes, ((_, _) =>
profiles.foreach(ShadowsocksApplication.profileManager.createProfile)): DialogInterface.OnClickListener)
.setNegativeButton(android.R.string.no, null)
- .setMessage(profiles.mkString("\n"))
+ .setMessage(sharedStr)
.create()
- dialog.setOnDismissListener((dialog: DialogInterface) => finish()) // builder method was added in API 17
+ dialog.setOnDismissListener((dialog: DialogInterface) => finish()) // builder method was added in API 17
dialog.show()
}
diff --git a/src/main/scala/com/github/shadowsocks/ProfileManagerActivity.scala b/src/main/scala/com/github/shadowsocks/ProfileManagerActivity.scala
index a28fe9bd..10b7bbec 100644
--- a/src/main/scala/com/github/shadowsocks/ProfileManagerActivity.scala
+++ b/src/main/scala/com/github/shadowsocks/ProfileManagerActivity.scala
@@ -1,6 +1,10 @@
package com.github.shadowsocks
+import java.nio.charset.Charset
+
import android.content._
+import android.nfc.{NdefRecord, NdefMessage, NfcEvent, NfcAdapter}
+import android.nfc.NfcAdapter.CreateNdefMessageCallback
import android.os.{Bundle, Handler}
import android.support.v7.app.{AlertDialog, AppCompatActivity}
import android.support.v7.widget.RecyclerView.ViewHolder
@@ -9,7 +13,7 @@ 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.{SpannableStringBuilder, Spanned}
+import android.text.{TextUtils, SpannableStringBuilder, Spanned}
import android.view.{LayoutInflater, MenuItem, View, ViewGroup}
import android.widget.{CheckedTextView, ImageView, LinearLayout, Toast}
import com.github.shadowsocks.aidl.IShadowsocksServiceCallback
@@ -24,16 +28,20 @@ import scala.collection.mutable.ArrayBuffer
/**
* @author Mygod
*/
-class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListener with ServiceBoundContext {
+class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListener with ServiceBoundContext with CreateNdefMessageCallback{
private class ProfileViewHolder(val view: View) extends RecyclerView.ViewHolder(view) with View.OnClickListener {
var item: Profile = _
private val text = itemView.findViewById(android.R.id.text1).asInstanceOf[CheckedTextView]
itemView.setOnClickListener(this)
{
- val qrcode = itemView.findViewById(R.id.qrcode)
- qrcode.setOnClickListener(_ => {
+ val shareBtn = itemView.findViewById(R.id.share)
+ shareBtn.setOnClickListener(_ => {
val url = Parser.generate(item)
+ if (nfcAdapter != null && nfcAdapter.isEnabled) {
+ nfcAdapter.setNdefPushMessageCallback(ProfileManagerActivity.this,ProfileManagerActivity.this)
+ nfcShareItem = url
+ }
val image = new ImageView(ProfileManagerActivity.this)
image.setLayoutParams(new LinearLayout.LayoutParams(-1, -1))
val qrcode = QRCode.from(url)
@@ -41,17 +49,24 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe
.asInstanceOf[QRCode].bitmap()
image.setImageBitmap(qrcode)
- new AlertDialog.Builder(ProfileManagerActivity.this)
+ val dialog = new AlertDialog.Builder(ProfileManagerActivity.this)
.setCancelable(true)
.setPositiveButton(R.string.close, null)
.setNegativeButton(R.string.copy_url, ((_, _) =>
clipboard.setPrimaryClip(ClipData.newPlainText(null, url))): DialogInterface.OnClickListener)
.setView(image)
+ .setMessage(R.string.share_message)
+ .setTitle(R.string.share)
.create()
- .show()
+ dialog.show()
+ dialog.setOnDismissListener((dialog : DialogInterface) => {
+ if (nfcAdapter != null && nfcAdapter.isEnabled) {
+ nfcAdapter.setNdefPushMessageCallback(null,ProfileManagerActivity.this)
+ }
+ })
})
- qrcode.setOnLongClickListener((v: View) => {
- Utils.positionToast(Toast.makeText(ProfileManagerActivity.this, R.string.qrcode, Toast.LENGTH_SHORT), qrcode,
+ shareBtn.setOnLongClickListener((v: View) => {
+ Utils.positionToast(Toast.makeText(ProfileManagerActivity.this, R.string.share, Toast.LENGTH_SHORT), shareBtn,
getWindow, 0, Utils.dpToPx(ProfileManagerActivity.this, 8)).show
true
})
@@ -148,6 +163,8 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe
private var undoManager: UndoSnackbarManager[Profile] = _
private lazy val clipboard = getSystemService(Context.CLIPBOARD_SERVICE).asInstanceOf[ClipboardManager]
+ private var nfcAdapter : NfcAdapter = _
+ private var nfcShareItem : String = _
override def onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
@@ -163,6 +180,11 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe
toolbar.inflateMenu(R.menu.profile_manager_menu)
toolbar.setOnMenuItemClickListener(this)
+ nfcAdapter = NfcAdapter.getDefaultAdapter(this)
+ if (nfcAdapter != null && nfcAdapter.isEnabled) {
+ nfcAdapter.setNdefPushMessageCallback(null,ProfileManagerActivity.this)
+ }
+
ShadowsocksApplication.profileManager.setProfileAddedListener(profilesAdapter.add)
val profilesList = findViewById(R.id.profilesList).asInstanceOf[RecyclerView]
val layoutManager = new LinearLayoutManager(this)
@@ -217,10 +239,16 @@ 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)
+ if (scanResult != null && !TextUtils.isEmpty(scanResult.getContents))
Parser.findAll(scanResult.getContents).foreach(ShadowsocksApplication.profileManager.createProfile)
}
+ def createNdefMessage(nfcEvent: NfcEvent): NdefMessage = {
+ val ndefRecord = new NdefRecord(NdefRecord.TNF_ABSOLUTE_URI,nfcShareItem.getBytes(Charset.forName("US-ASCII")),Array[Byte](),nfcShareItem.getBytes(Charset.forName("US-ASCII")))
+ val ndefMessage = new NdefMessage(Array[NdefRecord](ndefRecord))
+ ndefMessage
+ }
+
def onMenuItemClick(item: MenuItem): Boolean = item.getItemId match {
case R.id.scan_qr_code =>
val integrator = new IntentIntegrator(this)