From 8d6d4328f23b527be9ea794433b3f160cd58e5a5 Mon Sep 17 00:00:00 2001 From: Kent Date: Fri, 20 Mar 2015 06:31:18 +0800 Subject: [PATCH] Implemented #1162: Invoke NFC in Advanced View - Extracted NFC code from ViewKeyActivity to NfcHelper to share code between classes - Changed the private anonymous Handler for NFC into a static private subclass, that uses WeakReference to avoid memory leaks - Added resources needed (retrieved from Graphics) for the NFC button inside ViewKeyAdvShareFragment. - Fixed the ripple boundary of the Share With... button to prevent it from bleeding to other buttons on the right (UX improvement) --- .../keychain/ui/ViewKeyActivity.java | 169 ++------------- .../keychain/ui/ViewKeyAdvShareFragment.java | 21 +- .../keychain/util/NfcHelper.java | 204 ++++++++++++++++++ .../res/drawable-hdpi/ic_nfc_grey_24dp.png | Bin 0 -> 1059 bytes .../res/drawable-mdpi/ic_nfc_grey_24dp.png | Bin 0 -> 701 bytes .../res/drawable-xhdpi/ic_nfc_grey_24dp.png | Bin 0 -> 1359 bytes .../res/drawable-xxhdpi/ic_nfc_grey_24dp.png | Bin 0 -> 2040 bytes .../res/drawable-xxxhdpi/ic_nfc_grey_24dp.png | Bin 0 -> 2756 bytes .../layout/view_key_adv_share_fragment.xml | 25 ++- 9 files changed, 261 insertions(+), 158 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/NfcHelper.java create mode 100644 OpenKeychain/src/main/res/drawable-hdpi/ic_nfc_grey_24dp.png create mode 100644 OpenKeychain/src/main/res/drawable-mdpi/ic_nfc_grey_24dp.png create mode 100644 OpenKeychain/src/main/res/drawable-xhdpi/ic_nfc_grey_24dp.png create mode 100644 OpenKeychain/src/main/res/drawable-xxhdpi/ic_nfc_grey_24dp.png create mode 100644 OpenKeychain/src/main/res/drawable-xxxhdpi/ic_nfc_grey_24dp.png diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java index 5834fa502..45ad944c4 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -21,18 +21,12 @@ package org.sufficientlysecure.keychain.ui; import android.animation.ArgbEvaluator; import android.animation.ObjectAnimator; import android.annotation.SuppressLint; -import android.annotation.TargetApi; import android.app.Activity; import android.app.ActivityOptions; import android.content.Intent; -import android.content.pm.PackageManager; import android.database.Cursor; import android.graphics.Bitmap; import android.net.Uri; -import android.nfc.NdefMessage; -import android.nfc.NdefRecord; -import android.nfc.NfcAdapter; -import android.nfc.NfcEvent; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; @@ -40,7 +34,6 @@ import android.os.Handler; import android.os.Message; import android.os.Messenger; import android.provider.ContactsContract; -import android.provider.Settings; import android.support.v4.app.ActivityCompat; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; @@ -58,9 +51,7 @@ import android.widget.ImageView; import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; - import com.getbase.floatingactionbutton.FloatingActionButton; - import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.keyimport.ParcelableKeyRing; @@ -84,6 +75,7 @@ import org.sufficientlysecure.keychain.ui.util.QrCodeUtils; import org.sufficientlysecure.keychain.util.ContactHelper; import org.sufficientlysecure.keychain.util.ExportHelper; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.NfcHelper; import org.sufficientlysecure.keychain.util.Preferences; import java.util.ArrayList; @@ -93,8 +85,8 @@ public class ViewKeyActivity extends BaseActivity implements LoaderManager.LoaderCallbacks { static final int REQUEST_QR_FINGERPRINT = 1; - static final int REQUEST_DELETE= 2; - static final int REQUEST_EXPORT= 3; + static final int REQUEST_DELETE = 2; + static final int REQUEST_EXPORT = 3; ExportHelper mExportHelper; ProviderHelper mProviderHelper; @@ -115,11 +107,7 @@ public class ViewKeyActivity extends BaseActivity implements private CardView mQrCodeLayout; // NFC - private NfcAdapter mNfcAdapter; - private NfcAdapter.CreateNdefMessageCallback mNdefCallback; - private NfcAdapter.OnNdefPushCompleteCallback mNdefCompleteCallback; - private byte[] mNfcKeyringBytes; - private static final int NFC_SENT = 1; + private NfcHelper mNfcHelper; private static final int LOADER_ID_UNIFIED = 0; @@ -256,7 +244,7 @@ public class ViewKeyActivity extends BaseActivity implements mActionNfc.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - invokeNfcBeam(); + mNfcHelper.invokeNfcBeam(); } }); @@ -264,7 +252,8 @@ public class ViewKeyActivity extends BaseActivity implements // or start new ones. getSupportLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this); - initNfc(mDataUri); + mNfcHelper = new NfcHelper(this, mProviderHelper); + mNfcHelper.initNfc(mDataUri); startFragment(savedInstanceState, mDataUri); } @@ -375,41 +364,6 @@ public class ViewKeyActivity extends BaseActivity implements return true; } - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - private void invokeNfcBeam() { - // Check if device supports NFC - if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC)) { - Notify.createNotify(this, R.string.no_nfc_support, Notify.LENGTH_LONG, Notify.Style.ERROR).show(); - return; - } - // Check for available NFC Adapter - mNfcAdapter = NfcAdapter.getDefaultAdapter(this); - if (mNfcAdapter == null || !mNfcAdapter.isEnabled()) { - Notify.createNotify(this, R.string.error_nfc_needed, Notify.LENGTH_LONG, Notify.Style.ERROR, new Notify.ActionListener() { - @Override - public void onAction() { - Intent intentSettings = new Intent(Settings.ACTION_NFC_SETTINGS); - startActivity(intentSettings); - } - }, R.string.menu_nfc_preferences).show(); - - return; - } - - if (!mNfcAdapter.isNdefPushEnabled()) { - Notify.createNotify(this, R.string.error_beam_needed, Notify.LENGTH_LONG, Notify.Style.ERROR, new Notify.ActionListener() { - @Override - public void onAction() { - Intent intentSettings = new Intent(Settings.ACTION_NFCSHARING_SETTINGS); - startActivity(intentSettings); - } - }, R.string.menu_beam_preferences).show(); - - return; - } - - mNfcAdapter.invokeBeam(this); - } private void scanQrCode() { Intent scanQrCode = new Intent(this, ImportKeysProxyActivity.class); @@ -426,7 +380,7 @@ public class ViewKeyActivity extends BaseActivity implements private void certifyImmediate() { Intent intent = new Intent(this, CertifyKeyActivity.class); - intent.putExtra(CertifyKeyActivity.EXTRA_KEY_IDS, new long[]{mMasterKeyId}); + intent.putExtra(CertifyKeyActivity.EXTRA_KEY_IDS, new long[] {mMasterKeyId}); startCertifyIntent(intent); } @@ -487,11 +441,11 @@ public class ViewKeyActivity extends BaseActivity implements HashMap data = providerHelper.getGenericData( baseUri, - new String[]{KeychainContract.Keys.MASTER_KEY_ID, KeychainContract.KeyRings.HAS_SECRET}, - new int[]{ProviderHelper.FIELD_TYPE_INTEGER, ProviderHelper.FIELD_TYPE_INTEGER}); + new String[] {KeychainContract.Keys.MASTER_KEY_ID, KeychainContract.KeyRings.HAS_SECRET}, + new int[] {ProviderHelper.FIELD_TYPE_INTEGER, ProviderHelper.FIELD_TYPE_INTEGER}); exportHelper.showExportKeysDialog( - new long[]{(Long) data.get(KeychainContract.KeyRings.MASTER_KEY_ID)}, + new long[] {(Long) data.get(KeychainContract.KeyRings.MASTER_KEY_ID)}, Constants.Path.APP_DIR_FILE, ((Long) data.get(KeychainContract.KeyRings.HAS_SECRET) != 0) ); } catch (ProviderHelper.NotFoundException e) { @@ -515,7 +469,7 @@ public class ViewKeyActivity extends BaseActivity implements // Create a new Messenger for the communication back Messenger messenger = new Messenger(returnHandler); DeleteKeyDialogFragment deleteKeyDialog = DeleteKeyDialogFragment.newInstance(messenger, - new long[]{ mMasterKeyId }); + new long[] {mMasterKeyId}); deleteKeyDialog.show(getSupportFragmentManager(), "deleteKeyDialog"); } @@ -546,11 +500,11 @@ public class ViewKeyActivity extends BaseActivity implements return; } - if (requestCode == REQUEST_DELETE && resultCode == Activity.RESULT_OK){ + if (requestCode == REQUEST_DELETE && resultCode == Activity.RESULT_OK) { deleteKey(); } - if (requestCode == REQUEST_EXPORT && resultCode == Activity.RESULT_OK){ + if (requestCode == REQUEST_EXPORT && resultCode == Activity.RESULT_OK) { exportToFile(mDataUri, mExportHelper, mProviderHelper); } @@ -572,7 +526,7 @@ public class ViewKeyActivity extends BaseActivity implements long keyId = new ProviderHelper(this) .getCachedPublicKeyRing(dataUri) .extractOrGetMasterKeyId(); - long[] encryptionKeyIds = new long[]{keyId}; + long[] encryptionKeyIds = new long[] {keyId}; Intent intent; if (text) { intent = new Intent(this, EncryptTextActivity.class); @@ -710,98 +664,9 @@ public class ViewKeyActivity extends BaseActivity implements loadTask.execute(); } - /** - * NFC: Initialize NFC sharing if OS and device supports it - */ - @TargetApi(Build.VERSION_CODES.JELLY_BEAN) - private void initNfc(final Uri dataUri) { - // check if NFC Beam is supported (>= Android 4.1) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - - // Implementation for the CreateNdefMessageCallback interface - mNdefCallback = new NfcAdapter.CreateNdefMessageCallback() { - @Override - public NdefMessage createNdefMessage(NfcEvent event) { - /* - * When a device receives a push with an AAR in it, the application specified in the AAR is - * guaranteed to run. The AAR overrides the tag dispatch system. You can add it back in to - * guarantee that this activity starts when receiving a beamed message. For now, this code - * uses the tag dispatch system. - */ - return new NdefMessage(NdefRecord.createMime(Constants.NFC_MIME, - mNfcKeyringBytes), NdefRecord.createApplicationRecord(Constants.PACKAGE_NAME)); - } - }; - - // Implementation for the OnNdefPushCompleteCallback interface - mNdefCompleteCallback = new NfcAdapter.OnNdefPushCompleteCallback() { - @Override - public void onNdefPushComplete(NfcEvent event) { - // A handler is needed to send messages to the activity when this - // callback occurs, because it happens from a binder thread - mNfcHandler.obtainMessage(NFC_SENT).sendToTarget(); - } - }; - - // Check for available NFC Adapter - mNfcAdapter = NfcAdapter.getDefaultAdapter(this); - if (mNfcAdapter != null) { - /* - * Retrieve mNfcKeyringBytes here asynchronously (to not block the UI) - * and init nfc adapter afterwards. - * mNfcKeyringBytes can not be retrieved in createNdefMessage, because this process - * has no permissions to query the Uri. - */ - AsyncTask initTask = - new AsyncTask() { - protected Void doInBackground(Void... unused) { - try { - Uri blobUri = - KeychainContract.KeyRingData.buildPublicKeyRingUri(dataUri); - mNfcKeyringBytes = (byte[]) mProviderHelper.getGenericData( - blobUri, - KeychainContract.KeyRingData.KEY_RING_DATA, - ProviderHelper.FIELD_TYPE_BLOB); - } catch (ProviderHelper.NotFoundException e) { - Log.e(Constants.TAG, "key not found!", e); - } - - // no AsyncTask return (Void) - return null; - } - - protected void onPostExecute(Void unused) { - // Register callback to set NDEF message - mNfcAdapter.setNdefPushMessageCallback(mNdefCallback, - ViewKeyActivity.this); - // Register callback to listen for message-sent success - mNfcAdapter.setOnNdefPushCompleteCallback(mNdefCompleteCallback, - ViewKeyActivity.this); - } - }; - - initTask.execute(); - } - } - } - - /** - * NFC: This handler receives a message from onNdefPushComplete - */ - private final Handler mNfcHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case NFC_SENT: - Notify.showNotify( - ViewKeyActivity.this, R.string.nfc_successful, Notify.Style.INFO); - break; - } - } - }; // These are the rows that we will retrieve. - static final String[] PROJECTION = new String[]{ + static final String[] PROJECTION = new String[] { KeychainContract.KeyRings._ID, KeychainContract.KeyRings.MASTER_KEY_ID, KeychainContract.KeyRings.USER_ID, @@ -1018,4 +883,4 @@ public class ViewKeyActivity extends BaseActivity implements public void onLoaderReset(Loader loader) { } -} +} \ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java index 95a6faea9..29586ae9f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java @@ -52,6 +52,7 @@ import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.QrCodeUtils; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.NfcHelper; import java.io.IOException; @@ -68,10 +69,12 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements private View mFingerprintClipboardButton; private View mKeyShareButton; private View mKeyClipboardButton; + private View mKeyNfcButton; private ImageButton mKeySafeSlingerButton; private View mKeyUploadButton; ProviderHelper mProviderHelper; + NfcHelper mNfcHelper; private static final int LOADER_ID_UNIFIED = 0; @@ -83,6 +86,7 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements View view = inflater.inflate(R.layout.view_key_adv_share_fragment, getContainer()); mProviderHelper = new ProviderHelper(ViewKeyAdvShareFragment.this.getActivity()); + mNfcHelper = new NfcHelper(getActivity(), mProviderHelper); mFingerprint = (TextView) view.findViewById(R.id.view_key_fingerprint); mQrCode = (ImageView) view.findViewById(R.id.view_key_qr_code); @@ -90,6 +94,7 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements mFingerprintShareButton = view.findViewById(R.id.view_key_action_fingerprint_share); mFingerprintClipboardButton = view.findViewById(R.id.view_key_action_fingerprint_clipboard); mKeyShareButton = view.findViewById(R.id.view_key_action_key_share); + mKeyNfcButton = view.findViewById(R.id.view_key_action_key_nfc); mKeyClipboardButton = view.findViewById(R.id.view_key_action_key_clipboard); mKeySafeSlingerButton = (ImageButton) view.findViewById(R.id.view_key_action_key_safeslinger); mKeyUploadButton = view.findViewById(R.id.view_key_action_upload); @@ -128,6 +133,14 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements share(mDataUri, mProviderHelper, false, true); } }); + + mKeyNfcButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mNfcHelper.invokeNfcBeam(); + } + }); + mKeySafeSlingerButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -255,9 +268,12 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements // Prepare the loaders. Either re-connect with an existing ones, // or start new ones. getLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this); + + // Prepare the NfcHelper + mNfcHelper.initNfc(mDataUri); } - static final String[] UNIFIED_PROJECTION = new String[]{ + static final String[] UNIFIED_PROJECTION = new String[] { KeyRings._ID, KeyRings.MASTER_KEY_ID, KeyRings.HAS_ANY_SECRET, KeyRings.USER_ID, KeyRings.FINGERPRINT, KeyRings.ALGORITHM, KeyRings.KEY_SIZE, KeyRings.CREATION, KeyRings.IS_EXPIRED, @@ -362,4 +378,5 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements startActivityForResult(uploadIntent, 0); } -} + +} \ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/NfcHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/NfcHelper.java new file mode 100644 index 000000000..bae42d965 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/NfcHelper.java @@ -0,0 +1,204 @@ +package org.sufficientlysecure.keychain.util; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.nfc.NdefMessage; +import android.nfc.NdefRecord; +import android.nfc.NfcAdapter; +import android.nfc.NfcEvent; +import android.os.AsyncTask; +import android.os.Build; +import android.os.Handler; +import android.os.Message; +import android.provider.Settings; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.ui.util.Notify; + +import java.lang.ref.WeakReference; + +/** + * This class contains NFC functionality that can be shared across Fragments or Activities. + *

+ * Created on Mar 20, 2015. + * + * @author Kent + */ + +public class NfcHelper { + + private Activity mActivity; + private ProviderHelper mProviderHelper; + + /** + * NFC: This handler receives a message from onNdefPushComplete + */ + private static NfcHandler mNfcHandler; + + private NfcAdapter mNfcAdapter; + private NfcAdapter.CreateNdefMessageCallback mNdefCallback; + private NfcAdapter.OnNdefPushCompleteCallback mNdefCompleteCallback; + private byte[] mNfcKeyringBytes; + private static final int NFC_SENT = 1; + + /** + * Initializes the NfcHelper. + */ + public NfcHelper(final Activity activity, final ProviderHelper providerHelper) { + mActivity = activity; + mProviderHelper = providerHelper; + + mNfcHandler = new NfcHandler(mActivity); + } + + /** + * Return true if the NFC Adapter of this Helper has any features enabled. + * + * @return true if this NFC Adapter has any features enabled + */ + public boolean isEnabled() { + return mNfcAdapter.isEnabled(); + } + + /** + * NFC: Initialize NFC sharing if OS and device supports it + */ + @TargetApi(Build.VERSION_CODES.JELLY_BEAN) + public void initNfc(final Uri dataUri) { + // check if NFC Beam is supported (>= Android 4.1) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + + // Implementation for the CreateNdefMessageCallback interface + mNdefCallback = new NfcAdapter.CreateNdefMessageCallback() { + @Override + public NdefMessage createNdefMessage(NfcEvent event) { + /* + * When a device receives a push with an AAR in it, the application specified in the AAR is + * guaranteed to run. The AAR overrides the tag dispatch system. You can add it back in to + * guarantee that this activity starts when receiving a beamed message. For now, this code + * uses the tag dispatch system. + */ + return new NdefMessage(NdefRecord.createMime(Constants.NFC_MIME, + mNfcKeyringBytes), NdefRecord.createApplicationRecord(Constants.PACKAGE_NAME)); + } + }; + + // Implementation for the OnNdefPushCompleteCallback interface + mNdefCompleteCallback = new NfcAdapter.OnNdefPushCompleteCallback() { + @Override + public void onNdefPushComplete(NfcEvent event) { + // A handler is needed to send messages to the activity when this + // callback occurs, because it happens from a binder thread + mNfcHandler.obtainMessage(NFC_SENT).sendToTarget(); + } + }; + + // Check for available NFC Adapter + mNfcAdapter = NfcAdapter.getDefaultAdapter(mActivity); + if (mNfcAdapter != null) { + /* + * Retrieve mNfcKeyringBytes here asynchronously (to not block the UI) + * and init nfc adapter afterwards. + * mNfcKeyringBytes can not be retrieved in createNdefMessage, because this process + * has no permissions to query the Uri. + */ + AsyncTask initTask = + new AsyncTask() { + protected Void doInBackground(Void... unused) { + try { + Uri blobUri = + KeychainContract.KeyRingData.buildPublicKeyRingUri(dataUri); + mNfcKeyringBytes = (byte[]) mProviderHelper.getGenericData( + blobUri, + KeychainContract.KeyRingData.KEY_RING_DATA, + ProviderHelper.FIELD_TYPE_BLOB); + } catch (ProviderHelper.NotFoundException e) { + Log.e(Constants.TAG, "key not found!", e); + } + + // no AsyncTask return (Void) + return null; + } + + protected void onPostExecute(Void unused) { + // Register callback to set NDEF message + mNfcAdapter.setNdefPushMessageCallback(mNdefCallback, + mActivity); + // Register callback to listen for message-sent success + mNfcAdapter.setOnNdefPushCompleteCallback(mNdefCompleteCallback, + mActivity); + } + }; + + initTask.execute(); + } + } + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public void invokeNfcBeam() { + // Check if device supports NFC + if (!mActivity.getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC)) { + Notify.createNotify(mActivity, R.string.no_nfc_support, Notify.LENGTH_LONG, Notify.Style.ERROR).show(); + return; + } + // Check for available NFC Adapter + mNfcAdapter = NfcAdapter.getDefaultAdapter(mActivity); + if (mNfcAdapter == null || !mNfcAdapter.isEnabled()) { + Notify.createNotify(mActivity, R.string.error_nfc_needed, Notify.LENGTH_LONG, Notify.Style.ERROR, new Notify.ActionListener() { + @Override + public void onAction() { + Intent intentSettings = new Intent(Settings.ACTION_NFC_SETTINGS); + mActivity.startActivity(intentSettings); + } + }, R.string.menu_nfc_preferences).show(); + + return; + } + + if (!mNfcAdapter.isNdefPushEnabled()) { + Notify.createNotify(mActivity, R.string.error_beam_needed, Notify.LENGTH_LONG, Notify.Style.ERROR, new Notify.ActionListener() { + @Override + public void onAction() { + Intent intentSettings = new Intent(Settings.ACTION_NFCSHARING_SETTINGS); + mActivity.startActivity(intentSettings); + } + }, R.string.menu_beam_preferences).show(); + + return; + } + + mNfcAdapter.invokeBeam(mActivity); + } + + /** + * A static subclass of {@link Handler} with a {@link WeakReference} to an {@link Activity} to avoid memory leaks. + */ + private static class NfcHandler extends Handler { + private final WeakReference mActivityReference; + + public NfcHandler(Activity activity) { + mActivityReference = new WeakReference<>(activity); + } + + @Override + public void handleMessage(Message msg) { + Activity activity = mActivityReference.get(); + + if (activity != null) { + switch (msg.what) { + case NFC_SENT: + Notify.showNotify( + activity, R.string.nfc_successful, Notify.Style.INFO); + break; + } + } + } + } + +} \ No newline at end of file diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_nfc_grey_24dp.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_nfc_grey_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..fedf390138eb286be0e2ab911b43613d2c6d36fa GIT binary patch literal 1059 zcmV+;1l;?HP)k>o?mgcgkksv6a8S@g0$8pt@SzAb)UVKAR@YC%sm4G0|yem zTrU4kMBf;9`{3ZN5t z*l`>`%rM;O{Q7>7RwUEo@ZcE38VfZE1Z_aQk<}9-t-{6h!6f*TP3PU1 znp7Hb95>}R6$D_F?Pizn`yWdwhluDa7BgC@RK7O-=S~*?3yW!dAN<)6LTqDpy`2jE zm|k>hYHB%~%{~F(bpVkWdw_^e0eCbC&SWxYShCyMkzHWx-^_$wO6T7oD4WfmNUB6c zjXvRdURW#^UlBs=1(19nHr5;H*~!VtUsFMeB;yB*j-`}5aHeM_iFD@-D z?FI0vIl`X-1j)GTy4!>h!^Zv1w(ai$pin66fays>h(+_@{sS3of|;3_bGQcP=jS5; zueq)}U$58Kf*?50hF<{CV-g?pJTEr<<4FK}TG|uTE_~nrArQ^*8S7&ZV@I5!4LTYPD=xRt>;%sZ=_B-3xZn dMHl}Oe*-GcURS>JwHg2b002ovPDHLkV1m%8>`edw literal 0 HcmV?d00001 diff --git a/OpenKeychain/src/main/res/drawable-mdpi/ic_nfc_grey_24dp.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_nfc_grey_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..f8f6c3812e112d39901955316854e474f914d84f GIT binary patch literal 701 zcmV;u0z&=R3a!GScssFRc!7f*(R)& zcYA@HEY^xmDn&%C6fHC;BoKrYwn47YA_=bdk?hUbOu`a#ftZL*{>_{B=Dq*_y*Dth zhyM++8zhB7A*r>#FQxQKrP6%#PVS^R*LC9nN~rw(V0v5F~^U zRiGmliybW%i;LTyAeYMxWV6}Rkx&Sntkr6lCnhF-X|10EiB_w1we5-=$LY>yvnMv) zhY&?6<;#(gkxt`31khUF0FY9?HSxi=0$~_FkWzlh=kxb6nM`bhz#PzGo=&G_4M8e0Rn;61`o7-;mVutF1oYaNLWo%)t(5wh%jK@OTCIl3 zSzb@pT5ndrmB51OHmKL@=KvqDgb-7}3zPFLl79@qvaBW7b@u^>%-Y%tyfU5HzVA27 zx2#5G_MIBRm|0LI3~x_~1<)pgyV(P$)1ZMCgH7>46Qh})KBeO-%rp7&fz*$;dI zYC?#|CikLwhY!Xd1dvkBL^cJtZlisi`UIx^6m)axu&07F7@51yN zi81p&$8p}VEGr%+prWGU2!L_Lw9#>#+trxS(a`|_6Iy}lvF2M%P~Z#*n?f6kh}ua+ z{b5te^Sr-NxD^sJKdHt{opFW~Q^K;W>O7g4m^cq*(K7R`3lXp;z<#gq`vbOZKOXcT z5gnUtFaVs_cul_1+uM7Eh=vv4Z3_{wGQ@W?fRAn4ei=nhw7&13(@ez%W2B%E!zdVK zW>%+RH3ASOK%eWsYMcPKNLTFhTqB7_MTRoXWJKsudv48wQ~z(p;#7Qh+TbvFZmVHnjR zxc~snT&0<=Xk2}3fft)F0cw$v%F4=h0FX>3j{;Z^;8QKWN=kXyw(TTYED{Qz}TsocZk3=H(6VWFDsV;PNbq#8{>rl9}^s9bmuGRQZK<;1xp687- z^ELoy033_QY~QG@BxHLKb RYRCWp002ovPDHLkV1is>a?bz& literal 0 HcmV?d00001 diff --git a/OpenKeychain/src/main/res/drawable-xxhdpi/ic_nfc_grey_24dp.png b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_nfc_grey_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..7e8fa6ba2a71863a84244358daf567a7cb3ae955 GIT binary patch literal 2040 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 + android:gravity="center_vertical" + android:background="?android:selectableItemBackground"/> + + + + +