1 /* <lambda>null2 * 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.content.ComponentName 20 import android.content.pm.PackageManager 21 import android.os.Bundle 22 import androidx.appcompat.app.AppCompatActivity; 23 import androidx.activity.viewModels 24 import androidx.lifecycle.Observer 25 import com.google.android.material.button.MaterialButton 26 import com.google.android.material.textview.MaterialTextView 27 import java.io.BufferedReader 28 import java.io.InputStreamReader 29 import kotlinx.serialization.json.Json 30 import kotlinx.serialization.json.jsonArray 31 import kotlinx.serialization.json.jsonObject 32 33 class MainActivity : AppCompatActivity() { 34 35 private val viewModel: EmulatorViewModel by viewModels() 36 onCreatenull37 override fun onCreate(savedInstanceState: Bundle?) { 38 super.onCreate(savedInstanceState) 39 setContentView(R.layout.activity_main) 40 41 val observer = 42 Observer<EmulatorUiState> { state -> 43 val replayedFileText = "${getString(R.string.replayed_file_text)} ${state.snoopFile}" 44 findViewById<MaterialTextView>(R.id.snoop_file_name).text = replayedFileText 45 val logText = "${getString(R.string.log_text)}\n${state.transactionLog}" 46 findViewById<MaterialTextView>(R.id.transaction_log).text = logText 47 } 48 viewModel.uiState.observe(this, observer) 49 EmulatorHostApduService.viewModel = viewModel 50 51 findViewById<MaterialButton>(R.id.service_button).setOnClickListener { 52 startHostApduService() 53 findViewById<MaterialButton>(R.id.service_button).isEnabled = false 54 } 55 56 val snoopData = intent.getStringExtra(SNOOP_DATA_FLAG) 57 if (snoopData != null && snoopData.length > 0) { 58 val apdus = parseJsonString(snoopData) 59 updateService(apdus) 60 } 61 62 val snoopFile = intent.getStringExtra(SNOOP_FILE_FLAG) 63 if (snoopFile != null && snoopFile.length > 0) { 64 val apdus = openAndParseFile(PARSED_FILES_DIR + snoopFile) 65 updateService(apdus) 66 viewModel.setSnoopFile(snoopFile) 67 } 68 startHostApduService() 69 } 70 startHostApduServicenull71 private fun startHostApduService() { 72 packageManager.setComponentEnabledSetting( 73 ComponentName(this, EmulatorHostApduService::class.java), 74 PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 75 PackageManager.DONT_KILL_APP, 76 ) 77 } 78 79 /* Opens the snoop file and extracts all APDU commands and responses. */ openAndParseFilenull80 private fun openAndParseFile(file: String): List<ApduPair> { 81 val apduPairs = mutableListOf<ApduPair>() 82 val text = BufferedReader(InputStreamReader(getAssets().open(file))).use { it.readText() } 83 val lines = text.split("\n").toTypedArray() 84 for (line in lines) { 85 val arr = line.split(";").toTypedArray() 86 if (arr.size != 2) { // Should contain commands, followed by responses 87 continue 88 } 89 val commands = arr[0].split(",").toTypedArray() 90 val responses = arr[1].split(",").toTypedArray() 91 if (commands.size != responses.size) { 92 continue 93 } 94 for (i in commands.indices) { 95 val command = standardize(commands[i]) 96 val response = standardize(responses[i]) 97 apduPairs.add(ApduPair(command, response)) 98 } 99 } 100 return apduPairs 101 } 102 103 /** Extracts all APDU commands and responses from JSON string. */ parseJsonStringnull104 private fun parseJsonString(text: String): List<ApduPair> { 105 val apduPairs = mutableListOf<ApduPair>() 106 val array = Json.parseToJsonElement(text).jsonArray 107 for (element in array) { 108 val pair = element.jsonObject 109 val commands = pair.get("commands")?.jsonArray 110 val responses = pair.get("responses")?.jsonArray 111 if (commands == null || responses == null || commands.size != responses.size) { 112 continue 113 } 114 for (i in 0..<commands.size) { 115 val command = standardize(commands.get(i).toString()) 116 val response = standardize(responses.get(i).toString()) 117 apduPairs.add(ApduPair(command, response)) 118 } 119 } 120 return apduPairs 121 } 122 standardizenull123 private fun standardize(s: String): String { 124 return s.replace("[", "").replace("]", "").replace("\'", "").replace(" ", "").replace("\"", "") 125 } 126 127 /** Updates EmulatorHostApduService with the given APDU commands and responses. */ updateServicenull128 private fun updateService(apdus: List<ApduPair>) { 129 val hashmap: HashMap<String, MutableList<String>> = HashMap() 130 for (apduPair in apdus) { 131 val existingList = hashmap[apduPair.command] 132 if (existingList == null) { 133 hashmap[apduPair.command] = mutableListOf(apduPair.response) 134 } else { 135 existingList.add(apduPair.response) 136 hashmap[apduPair.command] = existingList 137 } 138 } 139 EmulatorHostApduService.apdus.postValue(hashmap) 140 } 141 142 companion object { 143 private const val TAG = "EmulatorHostApduServiceLog" 144 const val SNOOP_DATA_FLAG = "snoop_data" 145 const val SNOOP_FILE_FLAG = "snoop_file" 146 private const val PARSED_FILES_DIR = "src/com/android/nfc/emulatorapp/parsed_files/" 147 } 148 }