mirror of
https://github.com/nacabaro/vbhelper.git
synced 2026-07-30 00:31:54 +00:00
Make VBH-VW transfer DB owner via provider and shared transfer DAO
This commit is contained in:
parent
d40428ccfd
commit
895b704c8d
@ -2,6 +2,10 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<permission
|
||||
android:name="com.github.nacabaro.vbhelper.permission.ACCESS_TRANSFER_SEEN"
|
||||
android:protectionLevel="signature" />
|
||||
|
||||
<uses-permission android:name="android.permission.NFC" />
|
||||
<uses-feature android:name="android.hardware.nfc" android:required="true" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
@ -31,6 +35,12 @@
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/provider_paths" />
|
||||
</provider>
|
||||
<provider
|
||||
android:name=".transfer.SharedTransferSeenProvider"
|
||||
android:authorities="com.github.nacabaro.vbhelper.transferseen"
|
||||
android:exported="true"
|
||||
android:readPermission="com.github.nacabaro.vbhelper.permission.ACCESS_TRANSFER_SEEN"
|
||||
android:writePermission="com.github.nacabaro.vbhelper.permission.ACCESS_TRANSFER_SEEN" />
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
|
||||
@ -0,0 +1,22 @@
|
||||
package com.github.cfogrady.vitalwear.common.data
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.Room
|
||||
|
||||
object SharedDatabaseFactory {
|
||||
private const val DATABASE_NAME = "shared_transfer.db"
|
||||
|
||||
@Volatile
|
||||
private var instance: SharedTransferDatabase? = null
|
||||
|
||||
fun getDatabase(context: Context): SharedTransferDatabase {
|
||||
return instance ?: synchronized(this) {
|
||||
instance ?: Room.databaseBuilder(
|
||||
context.applicationContext,
|
||||
SharedTransferDatabase::class.java,
|
||||
DATABASE_NAME
|
||||
).build().also { instance = it }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,16 @@
|
||||
package com.github.cfogrady.vitalwear.common.data
|
||||
|
||||
import androidx.room.Database
|
||||
import androidx.room.RoomDatabase
|
||||
|
||||
@Database(
|
||||
entities = [
|
||||
SharedTransferSeenEntity::class,
|
||||
],
|
||||
version = 1,
|
||||
exportSchema = false,
|
||||
)
|
||||
abstract class SharedTransferDatabase : RoomDatabase() {
|
||||
abstract fun transferSeenDao(): SharedTransferSeenDao
|
||||
}
|
||||
|
||||
@ -0,0 +1,47 @@
|
||||
package com.github.cfogrady.vitalwear.common.data
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import androidx.room.Transaction
|
||||
|
||||
@Dao
|
||||
interface SharedTransferSeenDao {
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
fun insert(entry: SharedTransferSeenEntity): Long
|
||||
|
||||
@Query(
|
||||
"""
|
||||
UPDATE ${SharedTransferSeenEntity.TABLE}
|
||||
SET cardName = :cardName,
|
||||
seenAtEpochMillis = :seenAtEpochMillis
|
||||
WHERE cardLookupKey = :cardLookupKey
|
||||
AND slotId = :slotId
|
||||
"""
|
||||
)
|
||||
fun update(cardName: String, cardLookupKey: String, slotId: Int, seenAtEpochMillis: Long)
|
||||
|
||||
@Transaction
|
||||
fun markSeen(cardName: String, slotId: Int, seenAtEpochMillis: Long) {
|
||||
if (slotId < 0) {
|
||||
return
|
||||
}
|
||||
val cardLookupKey = cardName.lowercase().filter { it.isLetterOrDigit() }
|
||||
if (cardLookupKey.isBlank()) {
|
||||
return
|
||||
}
|
||||
val inserted = insert(
|
||||
SharedTransferSeenEntity(
|
||||
cardName = cardName,
|
||||
cardLookupKey = cardLookupKey,
|
||||
slotId = slotId,
|
||||
seenAtEpochMillis = seenAtEpochMillis,
|
||||
)
|
||||
)
|
||||
if (inserted == -1L) {
|
||||
update(cardName, cardLookupKey, slotId, seenAtEpochMillis)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,24 @@
|
||||
package com.github.cfogrady.vitalwear.common.data
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.Index
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity(
|
||||
tableName = SharedTransferSeenEntity.TABLE,
|
||||
indices = [
|
||||
Index(value = ["cardLookupKey", "slotId"], unique = true)
|
||||
]
|
||||
)
|
||||
data class SharedTransferSeenEntity(
|
||||
@PrimaryKey(autoGenerate = true) val id: Long = 0,
|
||||
val cardName: String,
|
||||
val cardLookupKey: String,
|
||||
val slotId: Int,
|
||||
val seenAtEpochMillis: Long,
|
||||
) {
|
||||
companion object {
|
||||
const val TABLE = "TransferSeen"
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,7 @@
|
||||
package com.github.nacabaro.vbhelper.daos
|
||||
|
||||
import com.github.cfogrady.vitalwear.common.data.SharedTransferSeenDao
|
||||
|
||||
@Deprecated("Use SharedTransferSeenDao from the shared transfer database.")
|
||||
typealias TransferSeenDao = SharedTransferSeenDao
|
||||
|
||||
@ -36,7 +36,7 @@ import com.github.nacabaro.vbhelper.domain.device_data.VitalWearCharacterSetting
|
||||
import com.github.nacabaro.vbhelper.domain.items.Items
|
||||
|
||||
@Database(
|
||||
version = 2,
|
||||
version = 5,
|
||||
entities = [
|
||||
Card::class,
|
||||
CardProgress::class,
|
||||
@ -104,5 +104,40 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val MIGRATION_2_3 = object : Migration(2, 3) {
|
||||
override fun migrate(db: SupportSQLiteDatabase) {
|
||||
val backfillTimestamp = System.currentTimeMillis()
|
||||
db.execSQL(
|
||||
"""
|
||||
INSERT INTO `TransformationHistory`(`monId`, `stageId`, `transformationDate`)
|
||||
SELECT uc.`id`, uc.`charId`, $backfillTimestamp
|
||||
FROM `UserCharacter` uc
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM `TransformationHistory` th
|
||||
WHERE th.`monId` = uc.`id`
|
||||
)
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val MIGRATION_3_5 = object : Migration(3, 5) {
|
||||
override fun migrate(db: SupportSQLiteDatabase) {
|
||||
// TransferSeen moved to shared_transfer.db
|
||||
db.execSQL("DROP TABLE IF EXISTS `TransferSeen`")
|
||||
db.execSQL("DROP INDEX IF EXISTS `index_TransferSeen_cardLookupKey_slotId`")
|
||||
}
|
||||
}
|
||||
|
||||
val MIGRATION_4_5 = object : Migration(4, 5) {
|
||||
override fun migrate(db: SupportSQLiteDatabase) {
|
||||
// TransferSeen moved to shared_transfer.db
|
||||
db.execSQL("DROP TABLE IF EXISTS `TransferSeen`")
|
||||
db.execSQL("DROP INDEX IF EXISTS `index_TransferSeen_cardLookupKey_slotId`")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -1,11 +1,13 @@
|
||||
package com.github.nacabaro.vbhelper.di
|
||||
|
||||
import com.github.cfogrady.vitalwear.common.data.SharedTransferSeenDao
|
||||
import com.github.nacabaro.vbhelper.database.AppDatabase
|
||||
import com.github.nacabaro.vbhelper.source.CurrencyRepository
|
||||
import com.github.nacabaro.vbhelper.source.DataStoreSecretsRepository
|
||||
|
||||
interface AppContainer {
|
||||
val db: AppDatabase
|
||||
val transferSeenDao: SharedTransferSeenDao
|
||||
val dataStoreSecretsRepository: DataStoreSecretsRepository
|
||||
val currencyRepository: CurrencyRepository
|
||||
}
|
||||
@ -4,8 +4,10 @@ import androidx.datastore.dataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import androidx.room.Room
|
||||
import com.github.nacabaro.vbhelper.database.AppDatabase
|
||||
import com.github.cfogrady.vitalwear.common.data.SharedDatabaseFactory
|
||||
import com.github.cfogrady.vitalwear.common.data.SharedTransferSeenDao
|
||||
import com.github.nacabaro.vbhelper.di.AppContainer
|
||||
import com.github.nacabaro.vbhelper.database.AppDatabase
|
||||
import com.github.nacabaro.vbhelper.source.CurrencyRepository
|
||||
import com.github.nacabaro.vbhelper.source.DataStoreSecretsRepository
|
||||
import com.github.nacabaro.vbhelper.source.SecretsSerializer
|
||||
@ -32,9 +34,16 @@ class DefaultAppContainer(private val context: Context) : AppContainer {
|
||||
)
|
||||
.createFromAsset("items.db")
|
||||
.addMigrations(AppDatabase.MIGRATION_1_2)
|
||||
.addMigrations(AppDatabase.MIGRATION_2_3)
|
||||
.addMigrations(AppDatabase.MIGRATION_3_5)
|
||||
.addMigrations(AppDatabase.MIGRATION_4_5)
|
||||
.build()
|
||||
}
|
||||
|
||||
override val transferSeenDao: SharedTransferSeenDao by lazy {
|
||||
SharedDatabaseFactory.getDatabase(context).transferSeenDao()
|
||||
}
|
||||
|
||||
override val dataStoreSecretsRepository = DataStoreSecretsRepository(context.secretsStore)
|
||||
|
||||
override val currencyRepository = CurrencyRepository(context.currencyStore)
|
||||
|
||||
@ -0,0 +1,7 @@
|
||||
package com.github.nacabaro.vbhelper.domain.transfer
|
||||
|
||||
import com.github.cfogrady.vitalwear.common.data.SharedTransferSeenEntity
|
||||
|
||||
@Deprecated("Use SharedTransferSeenEntity from the shared transfer database.")
|
||||
typealias TransferSeenEntity = SharedTransferSeenEntity
|
||||
|
||||
@ -66,6 +66,9 @@ class ScanScreenControllerImpl(
|
||||
0xF0.toByte(), 0x56, 0x49, 0x54, 0x41, 0x4C, 0x57, 0x45, 0x41, 0x52
|
||||
)
|
||||
private const val SW_OK = 0x9000
|
||||
// Lifecycle key for the always-on NFC suppressor that prevents Android from launching
|
||||
// com.android.apps.tag/.TagViewer whenever the watch's HCE comes into range.
|
||||
private const val LIFECYCLE_KEY_NFC_SUPPRESSOR = "nfc_suppressor"
|
||||
}
|
||||
|
||||
init {
|
||||
@ -75,6 +78,57 @@ class ScanScreenControllerImpl(
|
||||
}
|
||||
nfcAdapter = maybeNfcAdapter
|
||||
checkSecrets()
|
||||
registerNfcSuppressor()
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a lifecycle listener that keeps reader mode active (with a no-op callback)
|
||||
* whenever this Activity is in the foreground. This prevents Android's default NFC dispatch
|
||||
* system from launching com.android.apps.tag/.TagViewer when the watch's HCE service is
|
||||
* detected while the user hasn't explicitly pressed a transfer button yet.
|
||||
*
|
||||
* When the user presses Read/Write/CheckCard, [handleTag] replaces this suppressor with
|
||||
* the real transfer callback via [NfcAdapter.enableReaderMode]. After the transfer
|
||||
* completes, [enableNfcSuppressor] is called again to restore the passive suppressor.
|
||||
*/
|
||||
private fun registerNfcSuppressor() {
|
||||
registerActivityLifecycleListener(
|
||||
LIFECYCLE_KEY_NFC_SUPPRESSOR,
|
||||
object : ActivityLifecycleListener {
|
||||
override fun onResume() {
|
||||
enableNfcSuppressor()
|
||||
}
|
||||
override fun onPause() {
|
||||
disableReaderModeSafely()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables reader mode with a silent no-op callback. Calling [NfcAdapter.enableReaderMode]
|
||||
* suppresses Android's default tag-dispatch system (and therefore the "new tag scanned"
|
||||
* TagViewer screen) for as long as this Activity is in the foreground. The actual transfer
|
||||
* logic is wired up separately via [handleTag] when the user presses a button.
|
||||
*/
|
||||
private fun enableNfcSuppressor() {
|
||||
if (!nfcAdapter.isEnabled) return
|
||||
val options = Bundle()
|
||||
options.putInt(NfcAdapter.EXTRA_READER_PRESENCE_CHECK_DELAY, 250)
|
||||
val flags = NfcAdapter.FLAG_READER_NFC_A or
|
||||
NfcAdapter.FLAG_READER_NFC_B or
|
||||
NfcAdapter.FLAG_READER_NO_PLATFORM_SOUNDS or
|
||||
NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK
|
||||
runCatching {
|
||||
nfcAdapter.enableReaderMode(
|
||||
componentActivity,
|
||||
{ /* suppress default dispatch — user must tap a transfer button */ },
|
||||
flags,
|
||||
options
|
||||
)
|
||||
}.onFailure {
|
||||
Log.w("NFC", "Failed to enable NFC suppressor reader mode", it)
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Read (phone receives character FROM watch or bracelet) --------------------
|
||||
@ -103,7 +157,7 @@ class ScanScreenControllerImpl(
|
||||
},
|
||||
isoDepHandler = { isoDep ->
|
||||
val application = componentActivity.applicationContext as VBHelper
|
||||
val importer = VitalWearCharacterImporter(application.container.db)
|
||||
val importer = VitalWearCharacterImporter(application.container.db, application.container.transferSeenDao)
|
||||
try {
|
||||
var importResult: VitalWearCharacterImporter.ImportResult? = null
|
||||
val moved = VitalWearHceReaderClient(isoDep).moveCharacterFromWatch { character ->
|
||||
@ -176,7 +230,7 @@ class ScanScreenControllerImpl(
|
||||
runCatching {
|
||||
var importResult: VitalWearCharacterImporter.ImportResult? = null
|
||||
val moved = hceClient.moveCharacterFromWatch { character ->
|
||||
val result = VitalWearCharacterImporter(application.container.db).importCharacter(character)
|
||||
val result = VitalWearCharacterImporter(application.container.db, application.container.transferSeenDao).importCharacter(character)
|
||||
importResult = result
|
||||
result.success
|
||||
}
|
||||
@ -236,7 +290,9 @@ class ScanScreenControllerImpl(
|
||||
lastTagId = null
|
||||
lastTagHandledAtMs = 0L
|
||||
}
|
||||
disableReaderModeSafely()
|
||||
// Re-arm the suppressor so the TagViewer doesn't appear while the user is still
|
||||
// on the scan screen but hasn't pressed a transfer button yet.
|
||||
enableNfcSuppressor()
|
||||
}
|
||||
|
||||
override fun registerActivityLifecycleListener(key: String, activityLifecycleListener: ActivityLifecycleListener) {
|
||||
@ -370,7 +426,9 @@ class ScanScreenControllerImpl(
|
||||
Log.w("NFC", "Failed to ignore handled tag", it)
|
||||
}
|
||||
|
||||
disableReaderModeSafely()
|
||||
// Restore the passive suppressor so devices can remain close without Android
|
||||
// launching the TagViewer between transfers or while waiting for the next button press.
|
||||
enableNfcSuppressor()
|
||||
isHandlingTag.set(false)
|
||||
}
|
||||
|
||||
|
||||
@ -19,7 +19,8 @@ class FromNfcConverter (
|
||||
) {
|
||||
private val application = componentActivity.applicationContext as VBHelper
|
||||
private val database = application.container.db
|
||||
|
||||
private val transferSeenDao = application.container.transferSeenDao
|
||||
|
||||
|
||||
fun addCharacterUsingCard(
|
||||
nfcCharacter: NfcCharacter,
|
||||
@ -118,6 +119,9 @@ class FromNfcConverter (
|
||||
.userCharacterDao()
|
||||
.insertCharacterData(characterData)
|
||||
|
||||
val seenTimestamp = System.currentTimeMillis()
|
||||
markSeen(cardData, nfcCharacter.charIndex.toInt(), seenTimestamp)
|
||||
|
||||
if (nfcCharacter is BENfcCharacter) {
|
||||
addBeCharacterToDatabase(
|
||||
characterId = characterId,
|
||||
@ -292,12 +296,14 @@ class FromNfcConverter (
|
||||
|
||||
database
|
||||
.dexDao()
|
||||
.insertCharacter(
|
||||
item.toCharIndex.toInt(),
|
||||
dimData.id,
|
||||
date
|
||||
)
|
||||
.insertCharacter(item.toCharIndex.toInt(), dimData.id, date)
|
||||
transferSeenDao.markSeen(dimData.name, item.toCharIndex.toInt(), date)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun markSeen(cardData: Card, slotId: Int, timestamp: Long) {
|
||||
database.dexDao().insertCharacter(slotId, cardData.id, timestamp)
|
||||
transferSeenDao.markSeen(cardData.name, slotId, timestamp)
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
package com.github.nacabaro.vbhelper.source
|
||||
|
||||
import com.github.cfogrady.vbnfc.data.NfcCharacter
|
||||
import com.github.cfogrady.vitalwear.common.data.SharedTransferSeenDao
|
||||
import com.github.cfogrady.vitalwear.protos.Character
|
||||
import com.github.nacabaro.vbhelper.database.AppDatabase
|
||||
import com.github.nacabaro.vbhelper.domain.card.Card
|
||||
@ -13,7 +14,8 @@ import kotlinx.coroutines.runBlocking
|
||||
import kotlin.math.max
|
||||
|
||||
class VitalWearCharacterImporter(
|
||||
private val database: AppDatabase
|
||||
private val database: AppDatabase,
|
||||
private val transferSeenDao: SharedTransferSeenDao,
|
||||
) {
|
||||
data class ImportResult(
|
||||
val success: Boolean,
|
||||
@ -113,8 +115,9 @@ class VitalWearCharacterImporter(
|
||||
}
|
||||
|
||||
val now = System.currentTimeMillis()
|
||||
database.dexDao().insertCharacter(slotId, importedCard.id, now)
|
||||
markSeen(importedCard.name, slotId, importedCard.id, now)
|
||||
|
||||
var insertedTransformationCount = 0
|
||||
for (transformation in character.transformationHistoryList) {
|
||||
val transformationCard = resolveRelatedCard(
|
||||
incomingCardName = transformation.cardName,
|
||||
@ -129,10 +132,21 @@ class VitalWearCharacterImporter(
|
||||
transformationCard.id,
|
||||
now
|
||||
)
|
||||
database.dexDao().insertCharacter(transformation.slotId, transformationCard.id, now)
|
||||
insertedTransformationCount++
|
||||
markSeen(transformationCard.name, transformation.slotId, transformationCard.id, now)
|
||||
}
|
||||
}
|
||||
|
||||
if (insertedTransformationCount == 0) {
|
||||
// Keep HomeScreen renderable for freshly imported characters with empty history.
|
||||
database.userCharacterDao().insertTransformation(
|
||||
userCharacterId,
|
||||
slotId,
|
||||
importedCard.id,
|
||||
now
|
||||
)
|
||||
}
|
||||
|
||||
for ((cardName, maxAdventureCompleted) in character.maxAdventureCompletedByCardMap) {
|
||||
val adventureCard = resolveRelatedCard(
|
||||
incomingCardName = cardName,
|
||||
@ -210,6 +224,11 @@ class VitalWearCharacterImporter(
|
||||
private fun resolveDefaultAbilityRarity(): NfcCharacter.AbilityRarity {
|
||||
return enumValues<NfcCharacter.AbilityRarity>().first()
|
||||
}
|
||||
|
||||
private fun markSeen(cardName: String, slotId: Int, cardId: Long, timestamp: Long) {
|
||||
database.dexDao().insertCharacter(slotId, cardId, timestamp)
|
||||
transferSeenDao.markSeen(cardName, slotId, timestamp)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun selectImportedCard(
|
||||
|
||||
@ -0,0 +1,67 @@
|
||||
package com.github.nacabaro.vbhelper.transfer
|
||||
|
||||
import android.content.ContentProvider
|
||||
import android.content.ContentValues
|
||||
import android.database.Cursor
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import com.github.cfogrady.vitalwear.common.data.SharedDatabaseFactory
|
||||
|
||||
class SharedTransferSeenProvider : ContentProvider() {
|
||||
companion object {
|
||||
const val AUTHORITY = "com.github.nacabaro.vbhelper.transferseen"
|
||||
val URI: Uri = Uri.parse("content://$AUTHORITY")
|
||||
|
||||
const val METHOD_MARK_SEEN = "markSeen"
|
||||
const val EXTRA_CARD_NAME = "cardName"
|
||||
const val EXTRA_SLOT_ID = "slotId"
|
||||
const val EXTRA_SEEN_AT_EPOCH_MILLIS = "seenAtEpochMillis"
|
||||
const val RESULT_OK = "ok"
|
||||
}
|
||||
|
||||
override fun onCreate(): Boolean = true
|
||||
|
||||
override fun call(method: String, arg: String?, extras: Bundle?): Bundle {
|
||||
if (method != METHOD_MARK_SEEN) {
|
||||
return Bundle().apply { putBoolean(RESULT_OK, false) }
|
||||
}
|
||||
|
||||
val context = context ?: return Bundle().apply { putBoolean(RESULT_OK, false) }
|
||||
val cardName = extras?.getString(EXTRA_CARD_NAME).orEmpty()
|
||||
val slotId = extras?.getInt(EXTRA_SLOT_ID, -1) ?: -1
|
||||
val seenAt = extras?.getLong(EXTRA_SEEN_AT_EPOCH_MILLIS, 0L) ?: 0L
|
||||
|
||||
if (cardName.isBlank() || slotId < 0 || seenAt <= 0L) {
|
||||
return Bundle().apply { putBoolean(RESULT_OK, false) }
|
||||
}
|
||||
|
||||
return runCatching {
|
||||
SharedDatabaseFactory.getDatabase(context).transferSeenDao().markSeen(cardName, slotId, seenAt)
|
||||
Bundle().apply { putBoolean(RESULT_OK, true) }
|
||||
}.getOrElse {
|
||||
Bundle().apply { putBoolean(RESULT_OK, false) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun query(
|
||||
uri: Uri,
|
||||
projection: Array<out String>?,
|
||||
selection: String?,
|
||||
selectionArgs: Array<out String>?,
|
||||
sortOrder: String?
|
||||
): Cursor? = null
|
||||
|
||||
override fun getType(uri: Uri): String? = null
|
||||
|
||||
override fun insert(uri: Uri, values: ContentValues?): Uri? = null
|
||||
|
||||
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int = 0
|
||||
|
||||
override fun update(
|
||||
uri: Uri,
|
||||
values: ContentValues?,
|
||||
selection: String?,
|
||||
selectionArgs: Array<out String>?
|
||||
): Int = 0
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user