Płacisz zbliżeniowo telefonem? Zobacz jak to działa - część 2

07.12.2022 | Adam Nowicki

Wstęp

W listopadzie na naszym blogu pojawił się artykuł Krzysztofa Parusela na temat HCE, NFC oraz o płatnościach elektronicznych na świecie i w ING. Dziś zajmiemy się tematem pochodnym. Pokażę Wam jak wygląda komunikacja telefonu z anteną od strony kodu. Stworzymy aplikację, której zadaniem będzie znalezienie anteny w telefonie. To może znacznie ułatwić wykorzystanie telefonu do opłat za pośrednictwem terminala.  

 

Gdzie ta antena

Producenci telefonów montują anteny w różnych miejscach. Jeśli płacisz telefonem, wiesz już, w którym miejscu przyłożyć go do terminala aby zrealizować płatność. Jeśli jednak z okazji Świąt Bożego Narodzenia ktoś sprawi Ci miłą niespodziankę w postaci nowego modelu, to idąc do kasy możesz się zdziwić. Jeśli zbliżysz do terminala telefon zgodnie ze swoimi przyzwyczajeniami, może się okazać, że urządzenia się nie skomunikują. Powodem tego będzie inne niż w starym telefonie umiejscowienie anteny.

Znajomość położenia anteny byłaby przydatna szczególnie osobom starszym, które stresują się pierwszą transakcją mobilną, boją się, że coś może nie wyjść. O ile łatwiej byłoby im gdyby mogli przetestować taką płatność w warunkach domowych?

 

Jak to zrobimy?

Okazuje się, że plastikowe karty, które nosimy na co dzień w naszych portfelach potrafią komunikować się przez NFC z telefonem. Wiedząc o tym fakcie, możemy go wykorzystać w naszej aplikacji. Użytkownik będzie zbliżać kartę do różnych miejsc urządzenia. Jeśli połączenie zostanie nawiązane aplikacja może odtworzyć dźwięk lub pokazać grafikę sukcesu.

 

Do dzieła!

Tworzymy pustą aplikację androidową z pojedynczym activity. Potrzebujemy dwóch uprawnień, które od razu dodamy w AndroidManifest.xml. 

<uses-permission android:name="android.permission.VIBRATE" />

<uses-permission android:name="android.permission.NFC" />

 

Przejdźmy teraz do warstwy widoku. Najpierw dodamy trzy guziczki w activity_main.xml oraz obrazek ilustrujący co użytkownik powinien zrobić.

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:app="http://schemas.android.com/apk/res-auto"

    xmlns:tools="http://schemas.android.com/tools"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    tools:context=".MainActivity"

    android:orientation="vertical">

    <Button

        android:id="@+id/buttonSettings"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:text="@string/open_settings" />

    <Button

        android:id="@+id/buttonFind"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:text="@string/find_nfc" />

    <Button

        android:id="@+id/buttonCancel"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:text="@string/find_cancel" />

    <ImageView

        android:id="@+id/imageView"

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        app:srcCompat="@drawable/czytnik_nfc" />

</LinearLayout>

 

Efekt wygląda następująco. 

Źródło: materiały własne ING

 

Teraz kilka słów o akcjach na przyciskach

 

  • OPEN SETTINGS – otworzy ustawienia telefonu, żeby użytkownik mógł sprawdzić czy ma włączone NFC. Bez tego nic nie może zadziałać.
  • FIND NFC – aplikacja zaczyna skanowanie w trybie READER, w poszukiwaniu drugiego urządzenia, z którym można się komunikować.
  • FIND CANCEL – aplikacja kończy skanowanie.

 

Obsługę wszystkiego co związane z NFC postanowiłem wydzielić do osobnego modułu, żeby był on używalny poza testową aplikacją. Tworzymy więc nowy moduł o nazwie nfcfinder. W nim klasę o podobnej nazwie - NfcFinder wraz z odpowiednimi metodami, wywoływanymi z MainActivity.

 

class NfcFinder {

    fun enable() {

    }

    fun disable() {

    }

    fun openNFCSettings() {

    }

}

W konstruktorze chcemy dostawać activity, które ma obsługiwać komunikację z NFC oraz callback, pozwalający wrócić do activity po to by np. zawibrować albo wyświetlić komunikat.

Oprócz tego nie chcemy, żeby aplikacja mogła coś skanować gdy jest w tle. Trzeba tą sytuację jakoś obsłużyć, a najłatwiej wykorzystać do tego LifecycleObserver z androidx. Stwórzmy więc klasę:

class NfcFinderLifecycleObserver(private val callback: NfcLifecycleCallback) : LifecycleObserver {

    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)

    fun onResume() {

        callback.enable()

    }

    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)

    fun onPause() {

        callback.disable()

    }

}

Za chwilę podepniemy ją do cyklu życia naszego activity. Od tego momentu każde pójście w tło będzie skutkować zawołaniem onPause, a powrót onResume. Obsługę tych zdarzeń zlecimy naszej klasie NfcFinder, która będzie implementować interfejs NfcLifecycleCallback.

interface NfcLifecycleCallback {

    fun enable()

    fun disable()

}

Teraz możemy dokończyć implementację klasy NfcFinder.

class NfcFinder (

    private val activity: Activity,

    private var nfcFoundCallback: () -> (Unit)

) : NfcLifecycleCallback {

    override fun enable() {

        val nfcAdapter = NfcAdapter.getDefaultAdapter(activity)

        nfcAdapter?.enableReaderMode(activity, { _ ->

            this.nfcFoundCallback.invoke()

        }, NfcAdapter.FLAG_READER_NFC_A or NfcAdapter.FLAG_READER_NO_PLATFORM_SOUNDS, null)

    }

    override fun disable() {

        val nfcAdapter = NfcAdapter.getDefaultAdapter(activity)

        nfcAdapter?.disableReaderMode(activity)

    }

    fun openNFCSettings() {

        val intent = Intent(Settings.ACTION_NFC_SETTINGS)

        activity.startActivity(intent)

    }

}

Po nawiązaniu połączenia wywołamy wspomniany już nfcFoundCallback. Przejdźmy teraz do połączenia modułu nfcfinder z MainActivity.

override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState)

    setContentView(R.layout.activity_main)

    val nfcFinder = NfcFinder(this) { vibrate() }

    lifecycle.addObserver(

        NfcFinderLifecycleObserver(

            nfcFinder

        )

    )

    buttonSettings.setOnClickListener {

        nfcFinder.openNFCSettings()

    }

    buttonFind.setOnClickListener {

        nfcFinder.enable()

    }

    buttonCancel.setOnClickListener {

        nfcFinder.disable()

    }

}

Kod powinien być raczej jasny. Mamy tu stworzenie obiektu klasy NfcFinder, przekazanie mu callbacka oraz dodanie NfcFinderLifecycleObserver do obiektu lifecycle z androidxOprócz tego definiujemy obsługę przycisków.

Zostało nam jedynie poinformowanie użytkownika o zdarzeniu znalezienia karty/urządzenia NFC. Zrobimy to wibrując telefonem. Służy do tego klasa o nazwie Vibrator. Obsługa różni się w zależności od wersji systemu Android. Dodatkowo w logcat'cie wypiszemy odpowiedni tekst.

private fun vibrate() {

    Log.d("MainActivity", "NFC found")

    val v = getSystemService(Context.VIBRATOR_SERVICE) as Vibrator

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

        v.vibrate(VibrationEffect.createOneShot(200, VibrationEffect.DEFAULT_AMPLITUDE))

    } else {

        v.vibrate(200)

    }

}

 

Podsumowanie

Po przeczytaniu tego artkułu nie pozostaje nam nic innego jak uruchomić aplikację, kliknąć button FIND NFC i zbliżyć kartę do telefonu. Cały kod możecie znaleźć tutaj.