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 6c7602079..5027ed4d2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyAdvShareFragment.java @@ -27,7 +27,6 @@ import android.app.Activity; import android.app.ActivityOptions; import android.content.ClipData; import android.content.ClipboardManager; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.database.Cursor; @@ -39,6 +38,7 @@ import android.os.Build; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.support.v4.app.ActivityCompat; +import android.support.v4.app.FragmentActivity; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; @@ -94,9 +94,7 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements View root = super.onCreateView(inflater, superContainer, savedInstanceState); View view = inflater.inflate(R.layout.view_key_adv_share_fragment, getContainer()); - ContentResolver contentResolver = ViewKeyAdvShareFragment.this.getActivity().getContentResolver(); - KeyRepository keyRepository = KeyRepository.createDatabaseInteractor(getContext()); - mNfcHelper = new NfcHelper(getActivity(), keyRepository); + mNfcHelper = NfcHelper.getInstance(); mFingerprintView = (TextView) view.findViewById(R.id.view_key_fingerprint); mQrCode = (ImageView) view.findViewById(R.id.view_key_qr_code); @@ -176,7 +174,11 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements vKeyNfcButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - mNfcHelper.invokeNfcBeam(); + FragmentActivity activity = getActivity(); + if (activity == null) { + return; + } + mNfcHelper.invokeNfcBeam(activity); } }); } else { @@ -359,7 +361,9 @@ public class ViewKeyAdvShareFragment extends LoaderFragment implements getLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this); // Prepare the NfcHelper - mNfcHelper.initNfc(mDataUri); + FragmentActivity activity = getActivity(); + KeyRepository keyRepository = KeyRepository.createDatabaseInteractor(activity); + mNfcHelper.initNfcIfSupported(activity, keyRepository, mDataUri); } static final String[] UNIFIED_PROJECTION = new String[]{ diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyActivity.java index 88e92ead0..74ac0f41a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyActivity.java @@ -166,9 +166,6 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements private String mQrCodeLoaded; - // NFC - private NfcHelper mNfcHelper; - private static final int LOADER_ID_UNIFIED = 0; private boolean mIsSecret = false; @@ -321,10 +318,12 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements } }); + final NfcHelper nfcHelper = NfcHelper.getInstance(); + nfcHelper.initNfcIfSupported(this, mKeyRepository, mDataUri); mActionNfc.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - mNfcHelper.invokeNfcBeam(); + nfcHelper.invokeNfcBeam(ViewKeyActivity.this); } }); @@ -332,9 +331,6 @@ public class ViewKeyActivity extends BaseSecurityTokenActivity implements // or start new ones. getSupportLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this); - mNfcHelper = new NfcHelper(this, mKeyRepository); - mNfcHelper.initNfc(mDataUri); - if (savedInstanceState == null && getIntent().hasExtra(EXTRA_DISPLAY_RESULT)) { OperationResult result = getIntent().getParcelableExtra(EXTRA_DISPLAY_RESULT); result.createNotify(this).show(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/NfcHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/NfcHelper.java index f0136c37e..e01a78846 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/NfcHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/NfcHelper.java @@ -1,6 +1,6 @@ /* * Copyright (C) 2013-2014 Dominik Schürmann - * Copyright (C) 2015 Kent Nguyen + * Copyright (C) 2017 Vincent Breitmoser * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -29,12 +29,14 @@ import android.net.Uri; import android.nfc.NdefMessage; import android.nfc.NdefRecord; import android.nfc.NfcAdapter; +import android.nfc.NfcAdapter.CreateNdefMessageCallback; 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 android.support.annotation.NonNull; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; @@ -46,167 +48,156 @@ import org.sufficientlysecure.keychain.ui.util.Notify; /** * This class contains NFC functionality that can be shared across Fragments or Activities. */ - public class NfcHelper { - - private Activity mActivity; - private KeyRepository mKeyRepository; - - /** - * 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 KeyRepository keyRepository) { - mActivity = activity; - mKeyRepository = keyRepository; - mNfcHandler = new NfcHandler(mActivity); + public static NfcHelper getInstance() { + return new NfcHelper(); } - /** - * 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(); - } + private NfcHelper() { } + - /** - * NFC: Initialize NFC sharing if OS and device supports it - */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN) - public void initNfc(final Uri dataUri) { + public void initNfcIfSupported(final Activity activity, final KeyRepository keyRepository, final Uri dataUri) { // check if NFC Beam is supported (>= Android 4.1) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { + return; + } - // Implementation for the CreateNdefMessageCallback interface - mNdefCallback = new NfcAdapter.CreateNdefMessageCallback() { - @Override - public NdefMessage createNdefMessage(NfcEvent event) { + // Check for available NFC Adapter + final NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(activity); + if (nfcAdapter == null) { + return; + } + + final NfcHandler nfcHandler = new NfcHandler(activity); + + // Implementation for the OnNdefPushCompleteCallback interface + final NfcAdapter.OnNdefPushCompleteCallback ndefCompleteCallback = 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 + nfcHandler.obtainMessage(NFC_SENT).sendToTarget(); + } + }; + + /* Retrieve mNfcKeyringBytes here asynchronously (to not block the UI) and init nfc adapter afterwards. + * nfcKeyringBytes can not be retrieved in createNdefMessage, because this process has no permissions + * to query the Uri. + */ + AsyncTask initTask = new AsyncTask() { + protected byte[] doInBackground(Void... unused) { + try { + long masterKeyId = keyRepository.getCachedPublicKeyRing(dataUri).extractOrGetMasterKeyId(); + return keyRepository.loadPublicKeyRingData(masterKeyId); + } catch (NotFoundException | PgpKeyNotFoundException e) { + Log.e(Constants.TAG, "key not found!", e); + return null; + } catch (IllegalStateException e) { + // we specifically handle the database error if the blob is too large: "Couldn't read row 0, col 0 + // from CursorWindow. Make sure the Cursor is initialized correctly before accessing data from it." + if (!e.getMessage().startsWith("Couldn't read row")) { + throw e; + } + Log.e(Constants.TAG, "key blob too large to retrieve", e); + return null; + } + } + + protected void onPostExecute(final byte[] keyringBytes) { + if (keyringBytes == null) { + Log.e(Constants.TAG, "Could not obtain keyring bytes, NFC not available!"); + } + + if (activity.isFinishing()) { + return; + } + + // Implementation for the CreateNdefMessageCallback interface + CreateNdefMessageCallback ndefCallback = 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.MIME_TYPE_KEYS, - mNfcKeyringBytes), NdefRecord.createApplicationRecord(Constants.PACKAGE_NAME)); - } - }; + return new NdefMessage(NdefRecord.createMime(Constants.MIME_TYPE_KEYS, keyringBytes), + 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(); - } - }; + // Register callback to set NDEF message + nfcAdapter.setNdefPushMessageCallback(ndefCallback, activity); + // Register callback to listen for message-sent success + nfcAdapter.setOnNdefPushCompleteCallback(ndefCompleteCallback, activity); - // 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 { - long masterKeyId = mKeyRepository.getCachedPublicKeyRing(dataUri) - .extractOrGetMasterKeyId(); - mNfcKeyringBytes = mKeyRepository.loadPublicKeyRingData(masterKeyId); - } catch (NotFoundException | PgpKeyNotFoundException e) { - Log.e(Constants.TAG, "key not found!", e); - } - - // no AsyncTask return (Void) - return null; - } - - protected void onPostExecute(Void unused) { - if (mActivity.isFinishing()) { - return; - } - - // Register callback to set NDEF message - mNfcAdapter.setNdefPushMessageCallback(mNdefCallback, - mActivity); - // Register callback to listen for message-sent success - mNfcAdapter.setOnNdefPushCompleteCallback(mNdefCompleteCallback, - mActivity); - } - }; - - initTask.execute(); + Log.d(Constants.TAG, "NFC NDEF enabled!"); } - } + }; + + initTask.execute(); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) - public void invokeNfcBeam() { - // Check if device supports NFC - if (!mActivity.getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC)) { - Notify.create(mActivity, R.string.no_nfc_support, Notify.LENGTH_LONG, Notify.Style.ERROR).show(); + public void invokeNfcBeam(@NonNull final Activity activity) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { return; } - // Check for available NFC Adapter - mNfcAdapter = NfcAdapter.getDefaultAdapter(mActivity); - if (mNfcAdapter == null || !mNfcAdapter.isEnabled()) { - Notify.create(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); + + boolean hasNfc = activity.getPackageManager().hasSystemFeature(PackageManager.FEATURE_NFC); + if (!hasNfc) { + Notify.create(activity, R.string.no_nfc_support, Notify.LENGTH_LONG, Notify.Style.ERROR).show(); + return; + } + NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(activity); + + boolean isNfcEnabled = nfcAdapter != null && nfcAdapter.isEnabled(); + if (!isNfcEnabled) { + Notify.create(activity, 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); + activity.startActivity(intentSettings); } }, R.string.menu_nfc_preferences).show(); return; } - if (!mNfcAdapter.isNdefPushEnabled()) { - Notify.create(mActivity, R.string.error_beam_needed, Notify.LENGTH_LONG, Notify.Style.ERROR, new Notify.ActionListener() { + if (!nfcAdapter.isNdefPushEnabled()) { + Notify.create(activity, 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); + activity.startActivity(intentSettings); } }, R.string.menu_beam_preferences).show(); return; } - mNfcAdapter.invokeBeam(mActivity); + nfcAdapter.invokeBeam(activity); } private static class NfcHandler extends Handler { - private final WeakReference mActivityReference; + private final WeakReference activityReference; - public NfcHandler(Activity activity) { - mActivityReference = new WeakReference<>(activity); + NfcHandler(Activity activity) { + activityReference = new WeakReference<>(activity); } @Override public void handleMessage(Message msg) { - if (mActivityReference.get() != null) { + if (activityReference.get() != null) { switch (msg.what) { case NFC_SENT: - Notify.create(mActivityReference.get(), R.string.nfc_successful, Notify.Style.OK).show(); + Notify.create(activityReference.get(), R.string.nfc_successful, Notify.Style.OK).show(); break; } }