From f792c8ebfbfd38ed198b1ac3c13c779f2be2386c Mon Sep 17 00:00:00 2001 From: Lucas Liu Date: Wed, 16 Mar 2016 01:15:17 +0800 Subject: [PATCH 1/3] Fix parser bug --- src/main/scala/com/github/shadowsocks/ParserActivity.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/scala/com/github/shadowsocks/ParserActivity.scala b/src/main/scala/com/github/shadowsocks/ParserActivity.scala index 009cb0d7..e62e43fb 100644 --- a/src/main/scala/com/github/shadowsocks/ParserActivity.scala +++ b/src/main/scala/com/github/shadowsocks/ParserActivity.scala @@ -50,7 +50,8 @@ 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) + val data = getIntent.getData.toString + val profiles = Parser.findAll(data) if (profiles.isEmpty) { // ignore finish() return @@ -62,7 +63,7 @@ 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(data) .create() dialog.setOnDismissListener((dialog: DialogInterface) => finish()) // builder method was added in API 17 dialog.show() From 488d8256379e955a43dfe0560a95bd55ab6603c7 Mon Sep 17 00:00:00 2001 From: Lucas Liu Date: Wed, 16 Mar 2016 12:47:10 +0800 Subject: [PATCH 2/3] Add share profile by NFC. --- src/main/AndroidManifest.xml | 12 +++++- src/main/res/layout/layout_profiles_item.xml | 5 ++- src/main/res/values-zh/strings.xml | 3 +- src/main/res/values/strings.xml | 3 +- .../github/shadowsocks/ParserActivity.scala | 30 +++++++++---- .../shadowsocks/ProfileManagerActivity.scala | 42 +++++++++++++++---- 6 files changed, 76 insertions(+), 19 deletions(-) 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 e62e43fb..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,9 +51,24 @@ import com.github.shadowsocks.utils.Parser class ParserActivity extends AppCompatActivity { override def onCreate(savedInstanceState: Bundle) { super.onCreate(savedInstanceState) - val data = getIntent.getData.toString - val profiles = Parser.findAll(data) - 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 } @@ -63,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(data) + .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..06f32d75 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 @@ -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) @@ -221,6 +243,12 @@ class ProfileManagerActivity extends AppCompatActivity with OnMenuItemClickListe 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) From 7f2f67f27c834c7be9d4dcd5b4dda5a8b93b0320 Mon Sep 17 00:00:00 2001 From: Lucas Liu Date: Wed, 16 Mar 2016 13:22:53 +0800 Subject: [PATCH 3/3] Fix crash when cancel qrcode scan. --- .../scala/com/github/shadowsocks/ProfileManagerActivity.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/scala/com/github/shadowsocks/ProfileManagerActivity.scala b/src/main/scala/com/github/shadowsocks/ProfileManagerActivity.scala index 06f32d75..10b7bbec 100644 --- a/src/main/scala/com/github/shadowsocks/ProfileManagerActivity.scala +++ b/src/main/scala/com/github/shadowsocks/ProfileManagerActivity.scala @@ -13,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 @@ -239,7 +239,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) + if (scanResult != null && !TextUtils.isEmpty(scanResult.getContents)) Parser.findAll(scanResult.getContents).foreach(ShadowsocksApplication.profileManager.createProfile) }