• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 }