Add file selector
This commit is contained in:
3
.idea/deploymentTargetDropDown.xml
generated
3
.idea/deploymentTargetDropDown.xml
generated
@@ -2,6 +2,9 @@
|
||||
<project version="4">
|
||||
<component name="deploymentTargetDropDown">
|
||||
<value>
|
||||
<entry key="Reset">
|
||||
<State />
|
||||
</entry>
|
||||
<entry key="app">
|
||||
<State />
|
||||
</entry>
|
||||
|
@@ -76,6 +76,9 @@ dependencies {
|
||||
implementation("androidx.wear.compose:compose-foundation:1.2.1")
|
||||
implementation("androidx.activity:activity-compose:1.8.2")
|
||||
implementation("androidx.core:core-splashscreen:1.0.1")
|
||||
implementation("androidx.appcompat:appcompat:1.6.1")
|
||||
implementation("androidx.preference:preference:1.2.1")
|
||||
implementation("com.google.android.material:material:1.4.0")
|
||||
androidTestImplementation(platform("androidx.compose:compose-bom:2023.10.01"))
|
||||
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
|
||||
debugImplementation("androidx.compose.ui:ui-tooling")
|
||||
|
@@ -4,8 +4,12 @@
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.NFC" />
|
||||
|
||||
<uses-feature android:name="android.hardware.type.watch" android:required="true"/>
|
||||
<uses-feature android:name="android.hardware.nfc.hce" android:required="true"/>
|
||||
<uses-feature
|
||||
android:name="android.hardware.type.watch"
|
||||
android:required="true" />
|
||||
<uses-feature
|
||||
android:name="android.hardware.nfc.hce"
|
||||
android:required="true" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
@@ -13,6 +17,26 @@
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="false"
|
||||
android:theme="@android:style/Theme.DeviceDefault">
|
||||
|
||||
<activity
|
||||
android:name=".presentation.MainActivity"
|
||||
android:exported="true"
|
||||
android:taskAffinity=""
|
||||
android:theme="@style/MainActivityTheme.Starting">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".presentation.SettingsActivity"
|
||||
android:exported="false"
|
||||
android:label="@string/title_activity_settings"
|
||||
android:theme="@style/Theme.AppCompat.NoActionBar"
|
||||
android:taskAffinity="">
|
||||
</activity>
|
||||
|
||||
<service
|
||||
android:name=".MyHostApduService"
|
||||
android:exported="true"
|
||||
@@ -37,17 +61,7 @@
|
||||
android:name="com.google.android.wearable.standalone"
|
||||
android:value="true" />
|
||||
|
||||
<activity
|
||||
android:name=".presentation.MainActivity"
|
||||
android:exported="true"
|
||||
android:taskAffinity=""
|
||||
android:theme="@style/MainActivityTheme.Starting">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
@@ -10,12 +10,15 @@ import android.nfc.NdefRecord
|
||||
import android.nfc.cardemulation.HostApduService
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import androidx.preference.PreferenceManager
|
||||
import org.xmlpull.v1.XmlPullParser
|
||||
import java.io.File
|
||||
import java.io.UnsupportedEncodingException
|
||||
import java.math.BigInteger
|
||||
import java.util.LinkedList
|
||||
import java.util.Stack
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
|
||||
class MyHostApduService : HostApduService() {
|
||||
@@ -32,15 +35,18 @@ class MyHostApduService : HostApduService() {
|
||||
0x82.toByte(), // SW2 Status byte 2 - Command processing qualifier
|
||||
)
|
||||
|
||||
private var selectedNdefFile = "0000"
|
||||
private var selectedNdefFile = 0
|
||||
private val selectedNdefFilePath get() = String.format("%04X", this.selectedNdefFile)
|
||||
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
Log.i(TAG, "onStartCommand()")
|
||||
selectedNdefFile = "0000"
|
||||
selectedNdefFile = 0
|
||||
|
||||
return Service.START_STICKY
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
override fun processCommandApdu(commandApdu: ByteArray, extras: Bundle?): ByteArray {
|
||||
//
|
||||
// The following flow is based on Appendix E "Example of Mapping Version 2.0 Command Flow"
|
||||
@@ -67,18 +73,21 @@ class MyHostApduService : HostApduService() {
|
||||
when (apduDissect["Data"]) {
|
||||
"E103" -> { //CCFile
|
||||
Log.i(TAG, "NDEF file " + apduDissect["Data"] + "(CCF) selected")
|
||||
selectedNdefFile = apduDissect["Data"].toString()
|
||||
return A_OKAY
|
||||
}
|
||||
|
||||
"E104" -> {
|
||||
Log.i(TAG, "NDEF file " + apduDissect["Data"] + "(NDEF File) selected")
|
||||
selectedNdefFile = apduDissect["Data"].toString()
|
||||
selectedNdefFile = apduDissect["Data"].toString().hexToInt()
|
||||
return A_OKAY
|
||||
}
|
||||
|
||||
else -> {
|
||||
Log.w(TAG, "NDEF file " + apduDissect["Data"] + "select request denied")
|
||||
if(apduDissect["Data"]!!.toInt(16) < 0xE1FF) {
|
||||
Log.i(
|
||||
TAG,
|
||||
"NDEF file " + apduDissect["Data"] + "(NDEF File) selected"
|
||||
)
|
||||
selectedNdefFile = apduDissect["Data"].toString().hexToInt()
|
||||
return A_OKAY
|
||||
}
|
||||
|
||||
Log.w(TAG, "NDEF file " + apduDissect["Data"] + " select request denied")
|
||||
return A_ERROR
|
||||
}
|
||||
}
|
||||
@@ -92,19 +101,19 @@ class MyHostApduService : HostApduService() {
|
||||
}
|
||||
|
||||
"B0" -> { //Read data
|
||||
if (selectedNdefFile == "E103") { //CCfile
|
||||
if (selectedNdefFile == 0xE103) { //CCfile
|
||||
Log.i(TAG, "CCfile read")
|
||||
//TODO do better here
|
||||
return makeCCFileApdu()
|
||||
} else if (selectedNdefFile == "E104") {
|
||||
} else if ((selectedNdefFile >= 0xE104) and (selectedNdefFile <= 0xE1FF)) {
|
||||
|
||||
val offset = apduDissect["P1"].plus(apduDissect["P2"]).toInt(16)
|
||||
val length = apduDissect["Le"]?.toInt(16) ?: 0
|
||||
|
||||
val ndefFileContent: ByteArray = ByteArray(length + 2)
|
||||
val ndefFile = File(applicationContext.filesDir, selectedNdefFile)
|
||||
val ndefFileContent = ByteArray(length + 2)
|
||||
val ndefFile = File(applicationContext.filesDir, this.selectedNdefFilePath)
|
||||
if (!ndefFile.exists()) {
|
||||
applicationContext.openFileOutput(selectedNdefFile, Context.MODE_PRIVATE)
|
||||
applicationContext.openFileOutput(this.selectedNdefFilePath, Context.MODE_PRIVATE)
|
||||
.use {
|
||||
val ndefMsg = NdefMessage(createUrlRecord("https://example.com"))
|
||||
val ndefBytes = ndefMsg.toByteArray()
|
||||
@@ -116,7 +125,7 @@ class MyHostApduService : HostApduService() {
|
||||
it.write(ndefBytes)
|
||||
}
|
||||
}
|
||||
applicationContext.openFileInput(selectedNdefFile).use {
|
||||
applicationContext.openFileInput(this.selectedNdefFilePath).use {
|
||||
it.skip(offset.toLong())
|
||||
it.read(ndefFileContent, 0, length)
|
||||
}
|
||||
@@ -139,16 +148,16 @@ class MyHostApduService : HostApduService() {
|
||||
|
||||
val ccFile = parseCCFile()
|
||||
|
||||
val ndefFileContent: ByteArray =
|
||||
val ndefFileContent =
|
||||
ByteArray(Integer.decode(ccFile["NDEF_FILE_SIZE"] ?: "0000"))
|
||||
val ndefFile = File(applicationContext.filesDir, selectedNdefFile)
|
||||
val ndefFile = File(applicationContext.filesDir, this.selectedNdefFilePath)
|
||||
if (ndefFile.exists()) {
|
||||
applicationContext.openFileInput(selectedNdefFile).use {
|
||||
applicationContext.openFileInput(this.selectedNdefFilePath).use {
|
||||
it.read(ndefFileContent, 0, ndefFileContent.size)
|
||||
}
|
||||
}
|
||||
commandApdu.copyInto(ndefFileContent, offset, 5)
|
||||
applicationContext.openFileOutput(selectedNdefFile, Context.MODE_PRIVATE).use {
|
||||
applicationContext.openFileOutput(this.selectedNdefFilePath, Context.MODE_PRIVATE).use {
|
||||
it.write(ndefFileContent)
|
||||
}
|
||||
return A_OKAY
|
||||
@@ -195,6 +204,7 @@ class MyHostApduService : HostApduService() {
|
||||
return msg
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
private fun parseCCFile(): LinkedHashMap<String, String> {
|
||||
val xrp = resources.getXml(R.xml.ccfile)
|
||||
val parentTag = Stack<String>()
|
||||
@@ -220,10 +230,15 @@ class MyHostApduService : HostApduService() {
|
||||
}
|
||||
xrp.next()
|
||||
}
|
||||
|
||||
var id = ccHashMap["NDEF_FILE_ID"]?.drop(2)?.hexToInt() ?: 0xE104
|
||||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
id += sharedPreferences.getString("ndef_file_id","1")!!.toInt() - 1
|
||||
id = min(max(id,0xE104),0xE1FF)
|
||||
ccHashMap["NDEF_FILE_ID"] = String.format("0x%04X",id)
|
||||
return ccHashMap
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
private fun parseApdu(commandApdu: ByteArray): HashMap<String, String> {
|
||||
val dissected = HashMap<String, String>()
|
||||
dissected["CLA"] = commandApdu.copyOfRange(0, 1).toHex()
|
||||
@@ -240,7 +255,7 @@ class MyHostApduService : HostApduService() {
|
||||
|
||||
override fun onDeactivated(reason: Int) {
|
||||
Log.i(TAG, "onDeactivated() Fired! Reason: $reason")
|
||||
selectedNdefFile = "0000"
|
||||
selectedNdefFile = 0
|
||||
}
|
||||
|
||||
private val HEX_CHARS = "0123456789ABCDEF".toCharArray()
|
||||
@@ -259,20 +274,6 @@ class MyHostApduService : HostApduService() {
|
||||
return result.toString()
|
||||
}
|
||||
|
||||
fun String.hexStringToByteArray(): ByteArray {
|
||||
val result = ByteArray(length / 2)
|
||||
|
||||
for (i in indices step 2) {
|
||||
val firstIndex = HEX_CHARS.indexOf(this[i])
|
||||
val secondIndex = HEX_CHARS.indexOf(this[i + 1])
|
||||
|
||||
val octet = firstIndex.shl(4).or(secondIndex)
|
||||
result[i.shr(1)] = octet.toByte()
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private fun createTextRecord(language: String, text: String, id: ByteArray): NdefRecord {
|
||||
val languageBytes: ByteArray
|
||||
val textBytes: ByteArray
|
||||
@@ -316,15 +317,3 @@ class MyHostApduService : HostApduService() {
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
private fun Int.toByteArray(len: Int = 1): ByteArray {
|
||||
var str = toHexString(HexFormat.UpperCase).drop(2)
|
||||
str = str.padStart(len * 2, '0').takeLast(len * 2)
|
||||
val res = ByteArray(len)
|
||||
var i = 0
|
||||
str.chunked(2).forEach {
|
||||
res[i] = Integer.decode("0x$it").toByte()
|
||||
i++
|
||||
}
|
||||
return res
|
||||
}
|
@@ -6,9 +6,11 @@
|
||||
|
||||
package fr.ar2000.ndefemulator.presentation
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.widget.Button
|
||||
import android.widget.TextView
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
@@ -21,24 +23,37 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Devices
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.wear.compose.material.MaterialTheme
|
||||
import androidx.wear.compose.material.Text
|
||||
import androidx.wear.compose.material.TimeText
|
||||
import fr.ar2000.ndefemulator.R
|
||||
import fr.ar2000.ndefemulator.presentation.theme.NDEFEmulatorTheme
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
|
||||
class MainActivity : ComponentActivity(R.layout.main_activity) {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
installSplashScreen()
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setTheme(android.R.style.Theme_DeviceDefault)
|
||||
|
||||
setContent {
|
||||
WearApp()
|
||||
val settingButton = findViewById<Button>(R.id.setting_button)
|
||||
settingButton.setOnClickListener {
|
||||
val intent = Intent(this, SettingsActivity::class.java)
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
super.onResume()
|
||||
val currentFileId = findViewById<TextView>(R.id.current_file_id)
|
||||
currentFileId.text = getString(
|
||||
R.string.current_file_id,
|
||||
sharedPreferences.getString("ndef_file_id","1")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
@@ -0,0 +1,58 @@
|
||||
package fr.ar2000.ndefemulator.presentation
|
||||
|
||||
import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.EditText
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.fragment.app.FragmentContainerView
|
||||
import androidx.fragment.app.findFragment
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import androidx.preference.PreferenceManager
|
||||
import fr.ar2000.ndefemulator.R
|
||||
|
||||
class SettingsActivity : FragmentActivity(R.layout.settings_activity) {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
||||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
//supportFragmentManager.beginTransaction().replace(R.id.fragmentContainerView,MySettingsFragment()).commit()
|
||||
|
||||
val fileIdInput = findViewById<EditText>(R.id.ndef_file_id)
|
||||
fileIdInput.setOnFocusChangeListener { v, hasFocus ->
|
||||
if(!hasFocus){
|
||||
saveSetting()
|
||||
}
|
||||
}
|
||||
|
||||
if(savedInstanceState == null) {
|
||||
fileIdInput.setText(sharedPreferences.getString("ndef_file_id","1"))
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveSetting() {
|
||||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
val fileIdInput = findViewById<EditText>(R.id.ndef_file_id)
|
||||
val currentFileID = sharedPreferences.getString("ndef_file_id","1")
|
||||
val newFileID = fileIdInput.text.toString()
|
||||
if(TextUtils.isDigitsOnly((newFileID)) and (newFileID.toInt() > 0) and (newFileID.toInt() <= 252)) {
|
||||
sharedPreferences.edit().putString("ndef_file_id", newFileID).apply()
|
||||
}else{
|
||||
fileIdInput.setText(currentFileID)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
saveSetting()
|
||||
}
|
||||
}
|
||||
|
||||
class MySettingsFragment : PreferenceFragmentCompat() {
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.preferences,rootKey)
|
||||
}
|
||||
}
|
29
app/src/main/res/layout/main_activity.xml
Normal file
29
app/src/main/res/layout/main_activity.xml
Normal file
@@ -0,0 +1,29 @@
|
||||
<?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"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/hello_world"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Large" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/current_file_id"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Current file id : 1" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/setting_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/settings" />
|
||||
|
||||
</LinearLayout>
|
44
app/src/main/res/layout/settings_activity.xml
Normal file
44
app/src/main/res/layout/settings_activity.xml
Normal file
@@ -0,0 +1,44 @@
|
||||
<androidx.constraintlayout.widget.ConstraintLayout 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"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView4"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="NDEF file id"
|
||||
android:textAlignment="center" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/ndef_file_id"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ems="10"
|
||||
android:inputType="number"
|
||||
android:text="1"
|
||||
android:textAlignment="center" />
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/fragmentContainerView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
9
app/src/main/res/values-fr/strings.xml
Normal file
9
app/src/main/res/values-fr/strings.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">NDEF emulator</string>
|
||||
<string name="hello_world">Emulation d\'étiquette NDEF</string>
|
||||
<string name="servicedesc">Service démulation NFC</string>
|
||||
<string name="title_activity_settings">Options</string>
|
||||
<string name="settings">Options</string>
|
||||
<string name="current_file_id">Fichier NDEF : %S</string>
|
||||
</resources>
|
12
app/src/main/res/values/arrays.xml
Normal file
12
app/src/main/res/values/arrays.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<resources>
|
||||
<!-- Reply Preference -->
|
||||
<string-array name="reply_entries">
|
||||
<item>Reply</item>
|
||||
<item>Reply to all</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="reply_values">
|
||||
<item>reply</item>
|
||||
<item>reply_all</item>
|
||||
</string-array>
|
||||
</resources>
|
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="NDEF_AID">D2760000850101</string>
|
||||
<string name="NDEF_AID" translatable="false">D2760000850101</string>
|
||||
</resources>
|
@@ -2,5 +2,14 @@
|
||||
<string name="app_name">NDEF emulator</string>
|
||||
<string name="hello_world">Emulating NDEF tag</string>
|
||||
<string name="servicedesc">NFC emulator service</string>
|
||||
<string name="aiddescription">NDEF</string>
|
||||
<string name="aiddescription" translatable="false">NDEF</string>
|
||||
<string name="title_activity_settings">Settings</string>
|
||||
|
||||
<!-- Preference Titles -->
|
||||
|
||||
<!-- Messages Preferences -->
|
||||
|
||||
<!-- Sync Preferences -->
|
||||
<string name="settings">Settings</string>
|
||||
<string name="current_file_id">Current file id : %S</string>
|
||||
</resources>
|
13
app/src/main/res/xml/preferences.xml
Normal file
13
app/src/main/res/xml/preferences.xml
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<EditTextPreference
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:defaultValue="1"
|
||||
android:selectAllOnFocus="true"
|
||||
android:title="NDEF File id"
|
||||
android:inputType="number"
|
||||
app:key="ndef_file_id" />
|
||||
</PreferenceScreen>
|
Reference in New Issue
Block a user