diff --git a/common/build.gradle b/common/build.gradle index b1d67f689e..b7b6cafb13 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -7,9 +7,9 @@ apply plugin: 'dagger.hilt.android.plugin' android { defaultConfig { - compileSdk 33 + compileSdk 34 minSdkVersion 24 - targetSdkVersion 33 + targetSdkVersion 34 vectorDrawables.useSupportLibrary = true testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/common/src/main/java/org/dash/wallet/common/InteractionAwareActivity.java b/common/src/main/java/org/dash/wallet/common/InteractionAwareActivity.java index 52752b78be..2992f18cbe 100644 --- a/common/src/main/java/org/dash/wallet/common/InteractionAwareActivity.java +++ b/common/src/main/java/org/dash/wallet/common/InteractionAwareActivity.java @@ -20,6 +20,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.os.Build; import android.os.Bundle; import androidx.annotation.Nullable; @@ -33,7 +34,12 @@ public class InteractionAwareActivity extends SecureActivity { protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); IntentFilter filter = new IntentFilter(FORCE_FINISH_ACTION); - registerReceiver(forceFinishReceiver, filter); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + registerReceiver(forceFinishReceiver, filter, Context.RECEIVER_NOT_EXPORTED); + } else { + registerReceiver(forceFinishReceiver, filter); + } } @Override diff --git a/features/exploredash/build.gradle b/features/exploredash/build.gradle index 200c486a66..04a47c034d 100644 --- a/features/exploredash/build.gradle +++ b/features/exploredash/build.gradle @@ -10,9 +10,9 @@ plugins { android { defaultConfig { - compileSdk 33 + compileSdk 34 minSdkVersion 24 - targetSdkVersion 33 + targetSdkVersion 34 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" javaCompileOptions { @@ -115,7 +115,7 @@ dependencies { testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion" testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoVersion" testImplementation "androidx.arch.core:core-testing:$coreTestingVersion" - testImplementation "org.robolectric:robolectric:4.9.2" + testImplementation "org.robolectric:robolectric:4.13" testImplementation "androidx.room:room-testing:$roomVersion" androidTestImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoVersion" diff --git a/integrations/coinbase/build.gradle b/integrations/coinbase/build.gradle index 5b7f5afe21..36188c3452 100644 --- a/integrations/coinbase/build.gradle +++ b/integrations/coinbase/build.gradle @@ -10,9 +10,9 @@ plugins { android { defaultConfig { - compileSdk 33 + compileSdk 34 minSdkVersion 24 - targetSdkVersion 33 + targetSdkVersion 34 vectorDrawables.useSupportLibrary = true testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" diff --git a/integrations/crowdnode/build.gradle b/integrations/crowdnode/build.gradle index f61244b6d3..14f28ad80a 100644 --- a/integrations/crowdnode/build.gradle +++ b/integrations/crowdnode/build.gradle @@ -10,9 +10,9 @@ plugins { android { defaultConfig { - compileSdk 33 + compileSdk 34 minSdkVersion 24 - targetSdkVersion 33 + targetSdkVersion 34 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" } diff --git a/integrations/uphold/build.gradle b/integrations/uphold/build.gradle index b2cd751bf8..26be0fd8a6 100644 --- a/integrations/uphold/build.gradle +++ b/integrations/uphold/build.gradle @@ -9,9 +9,9 @@ plugins { android { defaultConfig { - compileSdk 33 + compileSdk 34 minSdkVersion 24 - targetSdkVersion 33 + targetSdkVersion 34 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } diff --git a/sample-integration-android/build.gradle b/sample-integration-android/build.gradle index 26723b640f..5aaa279088 100644 --- a/sample-integration-android/build.gradle +++ b/sample-integration-android/build.gradle @@ -38,9 +38,9 @@ android { } defaultConfig { - compileSdk 33 + compileSdk 34 minSdkVersion 24 - targetSdkVersion 33 + targetSdkVersion 34 multiDexEnabled true } compileOptions { diff --git a/wallet/AndroidManifest.xml b/wallet/AndroidManifest.xml index dbadd27fe7..a51370b5e5 100644 --- a/wallet/AndroidManifest.xml +++ b/wallet/AndroidManifest.xml @@ -8,12 +8,14 @@ - + + + @@ -21,6 +23,7 @@ + + android:exported="false" + android:foregroundServiceType="dataSync" /> + android:exported="false" + android:foregroundServiceType="connectedDevice" /> + + + + diff --git a/wallet/res/values/strings.xml b/wallet/res/values/strings.xml index eb9c16aa2a..b3ae515ac8 100644 --- a/wallet/res/values/strings.xml +++ b/wallet/res/values/strings.xml @@ -331,6 +331,7 @@ You still have Dash on this device! Please be sure to write down your recovery phrase before you uninstall the Dash Wallet or you will lose your balance of %s. Remind me later Don\'t remind me + Ready to receive payments via Bluetooth Dash balance diff --git a/wallet/src/de/schildbach/wallet/Constants.java b/wallet/src/de/schildbach/wallet/Constants.java index 1ca5af5c4a..34c0c07427 100644 --- a/wallet/src/de/schildbach/wallet/Constants.java +++ b/wallet/src/de/schildbach/wallet/Constants.java @@ -216,6 +216,7 @@ public final static class Files { public static final int NOTIFICATION_ID_INACTIVITY = 2; public static final int NOTIFICATION_ID_BLOCKCHAIN_SYNC = 3; public static final int NOTIFICATION_ID_UPGRADE_WALLET = 4; + public static final int NOTIFICATION_ID_BLUETOOTH = 5; public static String NOTIFICATION_CHANNEL_ID_TRANSACTIONS = "dash.notifications.transactions"; public static String NOTIFICATION_CHANNEL_ID_ONGOING = "dash.notifications.ongoing"; diff --git a/wallet/src/de/schildbach/wallet/offline/AcceptBluetoothService.java b/wallet/src/de/schildbach/wallet/offline/AcceptBluetoothService.java index e5e4828f3d..9bddeaddba 100644 --- a/wallet/src/de/schildbach/wallet/offline/AcceptBluetoothService.java +++ b/wallet/src/de/schildbach/wallet/offline/AcceptBluetoothService.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2015 the original author or authors. + * Copyright the original author or authors. * * 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 @@ -12,47 +12,62 @@ * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * along with this program. If not, see . */ package de.schildbach.wallet.offline; -import java.io.IOException; - -import org.bitcoinj.core.Transaction; -import org.bitcoinj.core.VerificationException; -import org.bitcoinj.wallet.Wallet; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import static com.google.common.base.Preconditions.checkNotNull; -import dagger.hilt.android.AndroidEntryPoint; -import de.schildbach.wallet.WalletApplication; -import de.schildbach.wallet.service.PackageInfoProvider; -import de.schildbach.wallet.util.CrashReporter; -import de.schildbach.wallet.util.Toast; -import de.schildbach.wallet_test.R; - -import android.app.Service; import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.ServiceInfo; +import android.os.Build; import android.os.Handler; import android.os.IBinder; import android.os.PowerManager; import android.os.PowerManager.WakeLock; import android.text.format.DateUtils; +import androidx.annotation.WorkerThread; +import androidx.core.app.NotificationCompat; +import androidx.lifecycle.LifecycleService; + +import org.bitcoinj.core.Transaction; +import org.bitcoinj.core.VerificationException; +import org.bitcoinj.wallet.Wallet; +import org.dash.wallet.common.WalletDataProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; + import javax.inject.Inject; +import dagger.hilt.android.AndroidEntryPoint; +import de.schildbach.wallet.Constants; +import de.schildbach.wallet.service.PackageInfoProvider; +import de.schildbach.wallet_test.R; +import de.schildbach.wallet.WalletApplication; +import de.schildbach.wallet.util.CrashReporter; +import de.schildbach.wallet.util.Toast; + /** * @author Andreas Schildbach */ @AndroidEntryPoint -public final class AcceptBluetoothService extends Service { - private WalletApplication application; - private Wallet wallet; +public final class AcceptBluetoothService extends LifecycleService { + @Inject + protected WalletApplication application; + @Inject + protected WalletDataProvider walletDataProvider; + + @Inject + protected PackageInfoProvider packageInfoProvider; private WakeLock wakeLock; private AcceptBluetoothThread classicThread; private AcceptBluetoothThread paymentProtocolThread; @@ -65,16 +80,16 @@ public final class AcceptBluetoothService extends Service { private static final Logger log = LoggerFactory.getLogger(AcceptBluetoothService.class); - @Inject - PackageInfoProvider packageInfoProvider; - @Override public IBinder onBind(final Intent intent) { + super.onBind(intent); return null; } @Override public int onStartCommand(final Intent intent, final int flags, final int startId) { + super.onStartCommand(intent, flags, startId); + handler.removeCallbacks(timeoutRunnable); handler.postDelayed(timeoutRunnable, TIMEOUT_MS); @@ -87,17 +102,27 @@ public void onCreate() { log.debug(".onCreate()"); super.onCreate(); + final BluetoothManager bluetoothManager = getSystemService(BluetoothManager.class); + final BluetoothAdapter bluetoothAdapter = checkNotNull(bluetoothManager.getAdapter()); + final PowerManager pm = getSystemService(PowerManager.class); - this.application = (WalletApplication) getApplication(); - this.wallet = application.getWallet(); - - final BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); - - final PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); - wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, - getPackageName() + " bluetooth transaction submission"); + wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, getClass().getName()); wakeLock.acquire(); + final NotificationCompat.Builder notification = new NotificationCompat.Builder(this, + Constants.NOTIFICATION_CHANNEL_ID_ONGOING); + notification.setColor(getColor(R.color.fg_network_significant)); + notification.setSmallIcon(R.drawable.stat_notify_bluetooth_24dp); + notification.setContentTitle(getString(R.string.notification_bluetooth_service_listening)); + notification.setWhen(System.currentTimeMillis()); + notification.setOngoing(true); + notification.setPriority(NotificationCompat.PRIORITY_LOW); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) + startForeground(Constants.NOTIFICATION_ID_BLUETOOTH, notification.build(), + ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE); + else + startForeground(Constants.NOTIFICATION_ID_BLUETOOTH, notification.build()); + registerReceiver(bluetoothStateChangeReceiver, new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)); try { @@ -113,35 +138,30 @@ public boolean handleTx(final Transaction tx) { return AcceptBluetoothService.this.handleTx(tx); } }; - - classicThread.start(); - paymentProtocolThread.start(); } catch (final IOException x) { new Toast(this).longToast(R.string.error_bluetooth, x.getMessage()); + log.warn("problem with listening, stopping service", x); CrashReporter.saveBackgroundTrace(x, packageInfoProvider.getPackageInfo()); + stopSelf(); } } + @WorkerThread private boolean handleTx(final Transaction tx) { - log.info("tx " + tx.getHashAsString() + " arrived via blueooth"); + log.info("tx {} arrived via blueooth", tx.getTxId()); + final Wallet wallet = walletDataProvider.getWallet(); try { if (wallet.isTransactionRelevant(tx)) { wallet.receivePending(tx, null); - - handler.post(new Runnable() { - @Override - public void run() { - application.broadcastTransaction(tx); - } - }); + handler.post(() -> application.broadcastTransaction(tx)); } else { - log.info("tx " + tx.getHashAsString() + " irrelevant"); + log.info("tx {} irrelevant", tx.getTxId()); } return true; } catch (final VerificationException x) { - log.info("cannot verify tx " + tx.getHashAsString() + " received via bluetooth", x); + log.info("cannot verify tx " + tx.getTxId() + " received via bluetooth", x); } return false; @@ -149,8 +169,10 @@ public void run() { @Override public void onDestroy() { - paymentProtocolThread.stopAccepting(); - classicThread.stopAccepting(); + if (paymentProtocolThread != null) + paymentProtocolThread.stopAccepting(); + if (classicThread != null) + classicThread.stopAccepting(); unregisterReceiver(bluetoothStateChangeReceiver); @@ -176,12 +198,9 @@ public void onReceive(final Context context, final Intent intent) { } }; - private final Runnable timeoutRunnable = new Runnable() { - @Override - public void run() { - log.info("timeout expired, stopping service"); + private final Runnable timeoutRunnable = () -> { + log.info("timeout expired, stopping service"); - stopSelf(); - } + stopSelf(); }; } diff --git a/wallet/src/de/schildbach/wallet/offline/AcceptBluetoothThread.java b/wallet/src/de/schildbach/wallet/offline/AcceptBluetoothThread.java index da7a7702f5..8d6f5be8b6 100644 --- a/wallet/src/de/schildbach/wallet/offline/AcceptBluetoothThread.java +++ b/wallet/src/de/schildbach/wallet/offline/AcceptBluetoothThread.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright the original author or authors. * * 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 @@ -12,15 +12,14 @@ * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * along with this program. If not, see . */ package de.schildbach.wallet.offline; -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.util.concurrent.atomic.AtomicBoolean; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothServerSocket; +import android.bluetooth.BluetoothSocket; import org.bitcoin.protocols.payments.Protos; import org.bitcoin.protocols.payments.Protos.PaymentACK; @@ -30,13 +29,14 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.concurrent.atomic.AtomicBoolean; + import de.schildbach.wallet.Constants; import de.schildbach.wallet.util.Bluetooth; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothServerSocket; -import android.bluetooth.BluetoothSocket; - /** * @author Shahar Livne * @author Andreas Schildbach @@ -62,19 +62,12 @@ public void run() { org.bitcoinj.core.Context.propagate(Constants.CONTEXT); while (running.get()) { - BluetoothSocket socket = null; - DataInputStream is = null; - DataOutputStream os = null; - - try { - // start a blocking call, and return only on success or exception - socket = listeningSocket.accept(); - + try ( // start a blocking call, and return only on success or exception + final BluetoothSocket socket = listeningSocket.accept(); + final DataInputStream is = new DataInputStream(socket.getInputStream()); + final DataOutputStream os = new DataOutputStream(socket.getOutputStream())) { log.info("accepted classic bluetooth connection"); - is = new DataInputStream(socket.getInputStream()); - os = new DataOutputStream(socket.getOutputStream()); - boolean ack = true; final int numMessages = is.readInt(); @@ -98,30 +91,6 @@ public void run() { os.writeBoolean(ack); } catch (final IOException x) { log.info("exception in bluetooth accept loop", x); - } finally { - if (os != null) { - try { - os.close(); - } catch (final IOException x) { - // swallow - } - } - - if (is != null) { - try { - is.close(); - } catch (final IOException x) { - // swallow - } - } - - if (socket != null) { - try { - socket.close(); - } catch (final IOException x) { - // swallow - } - } } } } @@ -138,19 +107,12 @@ public void run() { org.bitcoinj.core.Context.propagate(Constants.CONTEXT); while (running.get()) { - BluetoothSocket socket = null; - DataInputStream is = null; - DataOutputStream os = null; - - try { - // start a blocking call, and return only on success or exception - socket = listeningSocket.accept(); - + try ( // start a blocking call, and return only on success or exception + final BluetoothSocket socket = listeningSocket.accept(); + final DataInputStream is = new DataInputStream(socket.getInputStream()); + final DataOutputStream os = new DataOutputStream(socket.getOutputStream())) { log.info("accepted payment protocol bluetooth connection"); - is = new DataInputStream(socket.getInputStream()); - os = new DataOutputStream(socket.getOutputStream()); - boolean ack = true; final Protos.Payment payment = Protos.Payment.parseDelimitedFrom(is); @@ -171,30 +133,6 @@ public void run() { paymentAck.writeDelimitedTo(os); } catch (final IOException x) { log.info("exception in bluetooth accept loop", x); - } finally { - if (os != null) { - try { - os.close(); - } catch (final IOException x) { - // swallow - } - } - - if (is != null) { - try { - is.close(); - } catch (final IOException x) { - // swallow - } - } - - if (socket != null) { - try { - socket.close(); - } catch (final IOException x) { - // swallow - } - } } } } diff --git a/wallet/src/de/schildbach/wallet/service/BlockchainServiceImpl.java b/wallet/src/de/schildbach/wallet/service/BlockchainServiceImpl.java index 98036b7b23..273edcf240 100644 --- a/wallet/src/de/schildbach/wallet/service/BlockchainServiceImpl.java +++ b/wallet/src/de/schildbach/wallet/service/BlockchainServiceImpl.java @@ -28,6 +28,7 @@ import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; +import android.content.pm.ServiceInfo; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.Uri; @@ -1074,7 +1075,11 @@ private void startForeground() { //Shows ongoing notification promoting service to foreground service and //preventing it from being killed in Android 26 or later Notification notification = createNetworkSyncNotification(null); - startForeground(Constants.NOTIFICATION_ID_BLOCKCHAIN_SYNC, notification); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) + startForeground(Constants.NOTIFICATION_ID_BLOCKCHAIN_SYNC, notification, + ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC); + else + startForeground(Constants.NOTIFICATION_ID_BLOCKCHAIN_SYNC, notification); } @Override diff --git a/wallet/src/de/schildbach/wallet/ui/WalletAddressFragment.java b/wallet/src/de/schildbach/wallet/ui/WalletAddressFragment.java index 701a547bf3..bbad2d0857 100644 --- a/wallet/src/de/schildbach/wallet/ui/WalletAddressFragment.java +++ b/wallet/src/de/schildbach/wallet/ui/WalletAddressFragment.java @@ -33,6 +33,7 @@ import org.dash.wallet.common.Configuration; import de.schildbach.wallet.Constants; import de.schildbach.wallet.WalletApplication; +import de.schildbach.wallet.util.Nfc; import de.schildbach.wallet.util.ThrottlingWalletChangeListener; import de.schildbach.wallet_test.R; @@ -61,7 +62,7 @@ /** * @author Andreas Schildbach */ -public final class WalletAddressFragment extends Fragment implements NfcAdapter.CreateNdefMessageCallback { +public final class WalletAddressFragment extends Fragment { private Activity activity; private WalletApplication application; private Configuration config; @@ -91,9 +92,6 @@ public void onAttach(final Activity activity) { @Override public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); - - if (nfcAdapter != null && nfcAdapter.isEnabled()) - nfcAdapter.setNdefPushMessageCallback(this, activity); } @Override @@ -234,6 +232,9 @@ public void onLoadFinished(final Loader
loader, final Address currentAd currentAddressQrBitmap = Qr.INSTANCE.themeAwareDrawable(addressStr, getResources()); currentAddressUriRef.set(addressStr); + + Nfc.setNdefPushMessage(nfcAdapter, createNdefMessage(addressStr), activity); + updateView(); } } @@ -243,9 +244,7 @@ public void onLoaderReset(final Loader
loader) { } }; - @Override - public NdefMessage createNdefMessage(final NfcEvent event) { - final String uri = currentAddressUriRef.get(); + private static NdefMessage createNdefMessage(final String uri) { if (uri != null) return new NdefMessage(new NdefRecord[] { NdefRecord.createUri(uri) }); else diff --git a/wallet/src/de/schildbach/wallet/ui/WalletBalanceLoader.java b/wallet/src/de/schildbach/wallet/ui/WalletBalanceLoader.java index 0ec2a3366f..16f7e6d81b 100644 --- a/wallet/src/de/schildbach/wallet/ui/WalletBalanceLoader.java +++ b/wallet/src/de/schildbach/wallet/ui/WalletBalanceLoader.java @@ -41,7 +41,7 @@ * @author Andreas Schildbach */ public final class WalletBalanceLoader extends AsyncTaskLoader { - private LocalBroadcastManager broadcastManager; + private final LocalBroadcastManager broadcastManager; private final Wallet wallet; private static final Logger log = LoggerFactory.getLogger(WalletBalanceLoader.class); diff --git a/wallet/src/de/schildbach/wallet/ui/payments/ReceiveFragment.kt b/wallet/src/de/schildbach/wallet/ui/payments/ReceiveFragment.kt index 383f6c3d05..cb36dff9cd 100644 --- a/wallet/src/de/schildbach/wallet/ui/payments/ReceiveFragment.kt +++ b/wallet/src/de/schildbach/wallet/ui/payments/ReceiveFragment.kt @@ -37,6 +37,7 @@ import org.dash.wallet.common.ui.enter_amount.EnterAmountViewModel import org.dash.wallet.common.ui.viewBinding import javax.inject.Inject +// RequestCoinsFragment in Bitcoin Wallet has the code for Bluetooth support (sharing addresses) @AndroidEntryPoint class ReceiveFragment : Fragment(R.layout.fragment_receive) { private val enterAmountViewModel by activityViewModels() diff --git a/wallet/src/de/schildbach/wallet/util/Nfc.java b/wallet/src/de/schildbach/wallet/util/Nfc.java index 24f8ed5c6d..c3369efcbc 100644 --- a/wallet/src/de/schildbach/wallet/util/Nfc.java +++ b/wallet/src/de/schildbach/wallet/util/Nfc.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2015 the original author or authors. + * Copyright the original author or authors. * * 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 @@ -12,33 +12,37 @@ * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * along with this program. If not, see . */ package de.schildbach.wallet.util; -import java.util.Arrays; - -import javax.annotation.Nullable; - -import com.google.common.base.Charsets; - +import android.app.Activity; import android.nfc.NdefMessage; import android.nfc.NdefRecord; +import android.nfc.NfcAdapter; +import androidx.annotation.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.Method; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; /** * @author Andreas Schildbach */ public class Nfc { + private static final Logger log = LoggerFactory.getLogger(Nfc.class); + public static NdefRecord createMime(final String mimeType, final byte[] payload) { - final byte[] mimeBytes = mimeType.getBytes(Charsets.US_ASCII); - final NdefRecord mimeRecord = new NdefRecord(NdefRecord.TNF_MIME_MEDIA, mimeBytes, new byte[0], payload); - return mimeRecord; + final byte[] mimeBytes = mimeType.getBytes(StandardCharsets.US_ASCII); + return new NdefRecord(NdefRecord.TNF_MIME_MEDIA, mimeBytes, new byte[0], payload); } @Nullable public static byte[] extractMimePayload(final String mimeType, final NdefMessage message) { - final byte[] mimeBytes = mimeType.getBytes(Charsets.US_ASCII); + final byte[] mimeBytes = mimeType.getBytes(StandardCharsets.US_ASCII); for (final NdefRecord record : message.getRecords()) { if (record.getTnf() == NdefRecord.TNF_MIME_MEDIA && Arrays.equals(record.getType(), mimeBytes)) @@ -47,4 +51,18 @@ public static byte[] extractMimePayload(final String mimeType, final NdefMessage return null; } + + public static void setNdefPushMessage(final NfcAdapter adapter, final NdefMessage message, + final Activity activity) { + try { + // reflection hack needed for Android 14 and above + final Method setNdefPushMessage = adapter.getClass().getMethod("setNdefPushMessage", + NdefMessage.class, Activity.class, Activity[].class); + setNdefPushMessage.invoke(adapter, message, activity, new Activity[0]); + } catch (final ReflectiveOperationException x) { + log.info("problem setting NDEF push message", x); + } catch (final Exception x) { + throw new RuntimeException(x); + } + } }