diff --git a/app/build.gradle b/app/build.gradle index 2a82063..2fc06da 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,19 +1,17 @@ plugins { - alias(libs.plugins.android.application) + id 'com.android.application' } android { namespace 'de.androidcrypto.android_hce_beginner_app' - compileSdk 34 + compileSdk 34 // Atualizado para Android 14 defaultConfig { applicationId "de.androidcrypto.android_hce_beginner_app" - minSdk 21 - targetSdk 34 - versionCode 1 - versionName "1.0" - - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + minSdk 26 // Android 8.0 (compatível com maioria) + targetSdk 34 // Atualizado para Android 14 (Obrigatório) + versionCode 2 + versionName "2.0" } buildTypes { @@ -29,12 +27,7 @@ android { } dependencies { - - implementation libs.appcompat - implementation libs.material - implementation libs.activity - implementation libs.constraintlayout - testImplementation libs.junit - androidTestImplementation libs.ext.junit - androidTestImplementation libs.espresso.core -} \ No newline at end of file + implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'com.google.android.material:material:1.11.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' +} diff --git a/app/src/main/java/de/androidcrypto/android_hce_beginner_app/MainActivity.java b/app/src/main/java/de/androidcrypto/android_hce_beginner_app/MainActivity.java index 1373437..e6a5601 100644 --- a/app/src/main/java/de/androidcrypto/android_hce_beginner_app/MainActivity.java +++ b/app/src/main/java/de/androidcrypto/android_hce_beginner_app/MainActivity.java @@ -1,69 +1,53 @@ package de.androidcrypto.android_hce_beginner_app; -import android.content.ComponentName; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.nfc.NfcAdapter; -import android.nfc.cardemulation.CardEmulation; -import android.os.Bundle; - -import androidx.activity.EdgeToEdge; import androidx.appcompat.app.AppCompatActivity; -import androidx.core.graphics.Insets; -import androidx.core.view.ViewCompat; -import androidx.core.view.WindowInsetsCompat; -import androidx.fragment.app.Fragment; - -import com.google.android.material.bottomnavigation.BottomNavigationView; -import com.google.android.material.navigation.NavigationBarView; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.widget.Button; +import android.widget.EditText; +import android.widget.TextView; +import android.widget.Toast; public class MainActivity extends AppCompatActivity { + EditText etTrackData; + Button btnGravar; + TextView tvStatus; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - EdgeToEdge.enable(this); setContentView(R.layout.activity_main); - /* - ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> { - Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); - v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); - return insets; - }); - */ - - BottomNavigationView bottomNav = findViewById(R.id.bottom_navigation); - //bottomNav.setOnNavigationItemSelectedListener(navListener); - bottomNav.setOnItemSelectedListener(navListener); - - // as soon as the application opens the first - // fragment should be shown to the user - // in this case it is algorithm fragment - getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, new HomeFragment()).commit(); + // Busca os elementos do layout (que vamos criar no próximo passo) + etTrackData = findViewById(R.id.etTrackData); + btnGravar = findViewById(R.id.btnGravar); + tvStatus = findViewById(R.id.tvStatus); + + // 1. Recuperar track salva anteriormente + SharedPreferences prefs = getSharedPreferences("NfcData", MODE_PRIVATE); + // Valor padrão de exemplo + String savedTrack = prefs.getString("TRACK_DATA", "00A4040007F00102030405069000"); + etTrackData.setText(savedTrack); + + // 2. Configura o botão de Gravar + btnGravar.setOnClickListener(view -> { + String newData = etTrackData.getText().toString().trim(); + if (newData.isEmpty()) { + Toast.makeText(this, "A track não pode estar vazia!", Toast.LENGTH_SHORT).show(); + return; } - private final NavigationBarView.OnItemSelectedListener navListener = item -> { - // By using switch we can easily get - // the selected fragment - // by using there id. - Fragment selectedFragment = null; - int itemId = item.getItemId(); - if (itemId == R.id.home) { - selectedFragment = new HomeFragment(); - } else if (itemId == R.id.read) { - selectedFragment = new ReadFragment(); - } else if (itemId == R.id.write) { - selectedFragment = new WriteFragment(); - } - - // It will help to replace the - // one fragment to other. - if (selectedFragment != null) { - getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, selectedFragment).commit(); - } - return true; - }; + // Salva na memória permanente (SharedPreferences) + SharedPreferences.Editor editor = getSharedPreferences("NfcData", MODE_PRIVATE).edit(); + editor.putString("TRACK_DATA", newData); + editor.apply(); - -} \ No newline at end of file + tvStatus.setText("Track Gravada com Sucesso!\nPronto para aproximar da maquininha."); + Toast.makeText(this, "Dados NFC atualizados!", Toast.LENGTH_SHORT).show(); + }); + + // Exibe o status inicial + tvStatus.setText("Última Track carregada. Clique em Gravar para confirmar."); + } +} diff --git a/app/src/main/java/de/androidcrypto/android_hce_beginner_app/MyHostApduServiceSimple.java b/app/src/main/java/de/androidcrypto/android_hce_beginner_app/MyHostApduServiceSimple.java index 09e16db..042e385 100644 --- a/app/src/main/java/de/androidcrypto/android_hce_beginner_app/MyHostApduServiceSimple.java +++ b/app/src/main/java/de/androidcrypto/android_hce_beginner_app/MyHostApduServiceSimple.java @@ -1,190 +1,67 @@ -package de.androidcrypto.android_hce_beginner_app;/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +package de.androidcrypto.android_hce_beginner_app; import android.nfc.cardemulation.HostApduService; import android.os.Bundle; +import android.content.SharedPreferences; import android.util.Log; -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; +// Você precisará de uma classe Utils.java para a conversão de Hex, se ela não existir +// Por enquanto, vamos assumir que as conversões básicas estão em um arquivo auxiliar. -public class MyHostApduServiceSimple extends HostApduService { - private static final String TAG = "HceBeginnerApp"; - // ISO-DEP command HEADER for selecting an AID. - // Format: [Class | Instruction | Parameter 1 | Parameter 2] - private static final byte[] SELECT_APPLICATION_APDU = hexStringToByteArray("00a4040006f2233445566700"); - private static final String GET_DATA_APDU_HEADER = "00CA0000"; - private static final String PUT_DATA_APDU_HEADER = "00DA0000"; - // "OK" status word sent in response to SELECT AID command (0x9000) - private static final byte[] SELECT_OK_SW = hexStringToByteArray("9000"); - // "UNKNOWN" status word sent in response to invalid APDU command (0x0000) - private static final byte[] UNKNOWN_CMD_SW = hexStringToByteArray("0000"); +public class MyHostApduService extends HostApduService { - private byte[] fileContent01 = "HCE Beginner App 1".getBytes(StandardCharsets.UTF_8); - private byte[] fileContent02 = "HCE Beginner App 2".getBytes(StandardCharsets.UTF_8); - private byte[] fileContentUnknown = "HCE Beginner App Unknown".getBytes(StandardCharsets.UTF_8); - - /** - * Called if the connection to the NFC card is lost, in order to let the application know the - * cause for the disconnection (either a lost link, or another AID being selected by the - * reader). - * - * @param reason Either DEACTIVATION_LINK_LOSS or DEACTIVATION_DESELECTED - */ - @Override - public void onDeactivated(int reason) { + // Função de conversão de Hex para Byte Array (Se o projeto original não tiver) + // Se o Android Studio reclamar de Utils, crie um arquivo chamado Utils.java e coloque essas funções lá. + public static byte[] hexStringToByteArray(String s) { + int len = s.length(); + byte[] data = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + + Character.digit(s.charAt(i+1), 16)); + } + return data; } - /** - * This method will be called when a command APDU has been received from a remote device. A - * response APDU can be provided directly by returning a byte-array in this method. In general - * response APDUs must be sent as quickly as possible, given the fact that the user is likely - * holding his device over an NFC reader when this method is called. - * - *
If there are multiple services that have registered for the same AIDs in - * their meta-data entry, you will only get called if the user has explicitly selected your - * service, either as a default or just for the next tap. - * - *
This method is running on the main thread of your application. If you - * cannot return a response APDU immediately, return null and use the {@link - * #sendResponseApdu(byte[])} method later. - * - * @param commandApdu The APDU that received from the remote device - * @param extras A bundle containing extra data. May be null. - * @return a byte-array containing the response APDU, or null if no response APDU can be sent - * at this point. - */ - @Override public byte[] processCommandApdu(byte[] commandApdu, Bundle extras) { - // The following flow is based on Appendix E "Example of Mapping Version 2.0 Command Flow" - // in the NFC Forum specification - Log.i(TAG, "Received APDU: " + byteArrayToHexString(commandApdu)); - - // First command: Application select (Section 5.5.2 in NFC Forum spec) - if (Arrays.equals(SELECT_APPLICATION_APDU, commandApdu)) { - Log.i(TAG, "This is: 01 SELECT_APPLICATION_APDU"); - return SELECT_OK_SW; + if (commandApdu == null) { + return hexStringToByteArray("6F00"); // Erro genérico + } - // Second command: GetData command, here returning any data and not from file01 - } else if (arraysStartWith(commandApdu, hexStringToByteArray(GET_DATA_APDU_HEADER))) { - Log.i(TAG, "This is: 02 GET_DATA_APDU"); + // Simulação de SELECT AID (APDU inicial) + // Se a maquininha mandar um SELECT AID (00A40400...), respondemos com a track gravada + String hexCommand = bytesToHex(commandApdu); + Log.d("HCE_LOG", "Recebido da Maquininha: " + hexCommand); - // get the file number from commandApdu - int fileNumber; - byte[] fileContent; - if (commandApdu.length == 7) { - fileNumber = (int) commandApdu[5]; - if (fileNumber == 1) { - fileContent = fileContent01.clone(); - } else if (fileNumber == 2) { - fileContent = fileContent02.clone(); - } else { - fileContent = fileContentUnknown.clone(); - } - } else { - fileContent = "Unknown Request".getBytes(StandardCharsets.UTF_8); - } - byte[] response = new byte[fileContent.length + SELECT_OK_SW.length]; - System.arraycopy(fileContent, 0, response, 0, fileContent.length); - System.arraycopy(SELECT_OK_SW, 0, response, fileContent.length, SELECT_OK_SW.length); - Log.i(TAG, "GET_DATA_APDU Our Response: " + byteArrayToHexString(response)); - return response; - // write data to the emulated tag - } else if (arraysStartWith(commandApdu, hexStringToByteArray(PUT_DATA_APDU_HEADER))) { - Log.i(TAG, "This is: 03 PUT_DATA_APDU"); - // get the data length and file number from commandApdu - int dataLength; - int fileNumber; - byte[] fileContent; - if (commandApdu.length >= 7) { - dataLength = (int) commandApdu[4]; - fileNumber = (int) commandApdu[5]; - // System.out.println("commandApdu l: " + commandApdu.length + " data l: " + dataLength + " fnr: " + fileNumber); - // get the data - if (commandApdu.length != (6 + dataLength)) { - return UNKNOWN_CMD_SW; - } - fileContent = Arrays.copyOfRange(commandApdu, 6, (5 + dataLength)); - Log.i(TAG, "fileNr: " + fileNumber + " content: " + new String(fileContent, StandardCharsets.UTF_8)); - if (fileNumber == 1) { - fileContent01 = fileContent.clone(); - } else if (fileNumber == 2) { - fileContent02 = fileContent.clone(); - } else { - fileContentUnknown = fileContent.clone(); - } - } else { - fileContent = "Unknown Request".getBytes(StandardCharsets.UTF_8); - } - return SELECT_OK_SW; + // O comando de seleção geralmente começa com 00A40400 + if (hexCommand.startsWith("00A40400")) { + // Aqui pegamos a TRACK que você gravou na tela principal + SharedPreferences prefs = getSharedPreferences("NfcData", MODE_PRIVATE); + // Valor padrão: 9000 (Status OK). Se não tiver track gravada, o app continua funcionando. + String trackData = prefs.getString("TRACK_DATA", "9000"); + + Log.d("HCE_LOG", "Respondendo com Track gravada: " + trackData); + return hexStringToByteArray(trackData); + } - // We're doing something outside our scope - } else - Log.wtf(TAG, "processCommandApdu() | I don't know what's going on!!!."); - //return "Can I help you?".getBytes(); - return UNKNOWN_CMD_SW; + // Resposta padrão para outros comandos (Status OK) + return hexStringToByteArray("9000"); } - boolean arraysStartWith(byte[] completeArray, byte[] compareArray) { - int n = compareArray.length; - // Log.d(TAG, "completeArray length: " + completeArray.length + " data: " + ByteArrayToHexString(completeArray)); - // Log.d(TAG, "compareArray length: " + compareArray.length + " data: " + ByteArrayToHexString(compareArray)); - return ByteBuffer.wrap(completeArray, 0, n).equals(ByteBuffer.wrap(compareArray, 0, n)); + @Override + public void onDeactivated(int reason) { + Log.d("HCE_LOG", "Conexão terminada. Razão: " + reason); } - /** - * Utility method to convert a byte array to a hexadecimal string. - * - * @param bytes Bytes to convert - * @return String, containing hexadecimal representation. - */ - public static String byteArrayToHexString(byte[] bytes) { - final char[] hexArray = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; - char[] hexChars = new char[bytes.length * 2]; // Each byte has two hex characters (nibbles) - int v; + // Função de conversão de Byte Array para Hex String (Para Log) + private static String bytesToHex(byte[] bytes) { + final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray(); + char[] hexChars = new char[bytes.length * 2]; for (int j = 0; j < bytes.length; j++) { - v = bytes[j] & 0xFF; // Cast bytes[j] to int, treating as unsigned value - hexChars[j * 2] = hexArray[v >>> 4]; // Select hex character from upper nibble - hexChars[j * 2 + 1] = hexArray[v & 0x0F]; // Select hex character from lower nibble + int v = bytes[j] & 0xFF; + hexChars[j * 2] = HEX_ARRAY[v >>> 4]; + hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F]; } return new String(hexChars); } - - /** - * Utility method to convert a hexadecimal string to a byte string. - * - *
Behavior with input strings containing non-hexadecimal characters is undefined.
- *
- * @param s String containing hexadecimal characters to convert
- * @return Byte array generated from input
- * @throws IllegalArgumentException if input length is incorrect
- */
- public static byte[] hexStringToByteArray(String s) throws IllegalArgumentException {
- int len = s.length();
- if (len % 2 == 1) {
- throw new IllegalArgumentException("Hex string must have even number of characters");
- }
- byte[] data = new byte[len / 2]; // Allocate 1 byte per 2 hex characters
- for (int i = 0; i < len; i += 2) {
- // Convert each character into a integer (base-16), then bit-shift into place
- data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
- + Character.digit(s.charAt(i + 1), 16));
- }
- return data;
- }
}
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index ad5edc7..47d8cc1 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -1,24 +1,50 @@
-