1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.nfc.emulatorapp 18 19 import android.nfc.cardemulation.HostApduService 20 import android.nfc.cardemulation.PollingFrame 21 import android.os.Bundle 22 import androidx.lifecycle.MutableLiveData 23 import java.math.BigInteger 24 25 /** 26 * Implementation of HostApduService that receives APDU commands from the reader and sends back 27 * responses. 28 */ 29 class EmulatorHostApduService : HostApduService() { 30 processPollingFramesnull31 override fun processPollingFrames(frames: List<PollingFrame>) { 32 for (frame in frames) { 33 viewModel.addLog("Received polling frame ${frame.toString()}") 34 } 35 } 36 37 /** 38 * Processes the APDU command received from the reader and sends back a response. If the command 39 * was not found in the original snoop log, a failure response is sent instead. 40 */ processCommandApdunull41 override fun processCommandApdu(commandApdu: ByteArray?, extras: Bundle?): ByteArray { 42 val command = commandApdu?.toHexString() 43 val responseList = apdus.value?.get(command) 44 if (responseList != null && responseList.isNotEmpty()) { 45 val response = responseList.removeAt(0) 46 viewModel.addLog(createApduLog(command, response)) 47 return if (response.isEmpty()) ByteArray(0) else BigInteger(response, 16).toByteArray() 48 } else { 49 viewModel.addLog(createApduLog(command, FAILURE_RESPONSE.toHexString())) 50 return FAILURE_RESPONSE 51 } 52 } 53 onDeactivatednull54 override fun onDeactivated(reason: Int) { 55 if (reason == DEACTIVATION_LINK_LOSS) { 56 viewModel.addLog("Service has been deactivated due to NFC link loss") 57 } else { // DEACTIVATION_DESELECTED 58 viewModel.addLog("Service has been deactivated due to a different AID being selected") 59 } 60 } 61 toHexStringnull62 private fun ByteArray.toHexString(): String { 63 return this.joinToString("") { java.lang.String.format("%02x", it) } 64 } 65 createApduLognull66 private fun createApduLog(command: String?, response: String): String { 67 if (command == null) { 68 return "Received null command" 69 } 70 if (responseDecoder.containsKey(response.lowercase())) { 71 return "Received command: $command\n\n Sent response: ${responseDecoder[response]}" 72 } else { 73 return "Received command: $command\n\n Sent response: $response" 74 } 75 } 76 77 companion object { 78 private const val TAG = "EmulatorHostApduServiceLog" 79 private val FAILURE_RESPONSE = BigInteger("6f00", 16).toByteArray() 80 val apdus = MutableLiveData<Map<String, MutableList<String>>>() 81 val responseDecoder = mapOf("6f00" to "Failure", "6a82" to "AID not found") 82 lateinit var viewModel: EmulatorViewModel 83 } 84 }