diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index eebabdfba..08282a725 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -25,8 +25,9 @@ If applicable, add a video and/or screenshots to help explain your problem.
**Device (please complete the following information):**
- Phone Brand/Model [e.g. Pixel 7 Pro]:
- - Android Version [e.g. 33]:
- - App Version [e.g. v0.20.3]:
+ - Android Version [e.g. 34]:
+ - App Version [e.g. v0.94.3]:
+ - App Flavour [e.g. Google Play or FDroid]:
- Amber Version (if using it to sign):
**Bounty (in Bitcoin sats) offered for a solution**
diff --git a/amethyst/build.gradle b/amethyst/build.gradle
index 75d9ca054..8a22cb0f6 100644
--- a/amethyst/build.gradle
+++ b/amethyst/build.gradle
@@ -108,7 +108,6 @@ android {
release {
proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), 'proguard-rules.pro'
minifyEnabled true
- resValue "string", "app_name", "@string/app_name_release"
}
debug {
applicationIdSuffix '.debug'
diff --git a/amethyst/proguard-rules.pro b/amethyst/proguard-rules.pro
index c84569d50..830dc4248 100644
--- a/amethyst/proguard-rules.pro
+++ b/amethyst/proguard-rules.pro
@@ -61,7 +61,5 @@
# JSON parsing
-keep class com.vitorpamplona.quartz.** { *; }
--keep class com.vitorpamplona.amethyst.model.** { *; }
--keep class com.vitorpamplona.amethyst.service.** { *; }
--keep class com.vitorpamplona.ammolite.service.** { *; }
--keep class com.vitorpamplona.ammolite.relays.** { *; }
+-keep class com.vitorpamplona.amethyst.** { *; }
+-keep class com.vitorpamplona.ammolite.** { *; }
diff --git a/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/DMDecryptionTest.kt b/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/DMFileDecryptionTest.kt
similarity index 79%
rename from amethyst/src/androidTest/java/com/vitorpamplona/amethyst/DMDecryptionTest.kt
rename to amethyst/src/androidTest/java/com/vitorpamplona/amethyst/DMFileDecryptionTest.kt
index ed80e2f77..0c53bf220 100644
--- a/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/DMDecryptionTest.kt
+++ b/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/DMFileDecryptionTest.kt
@@ -21,17 +21,20 @@
package com.vitorpamplona.amethyst
import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.vitorpamplona.amethyst.service.okhttp.HttpClientManager
+import com.vitorpamplona.amethyst.service.okhttp.EncryptedBlobInterceptor
+import com.vitorpamplona.amethyst.service.okhttp.EncryptionKeyCache
import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray
import com.vitorpamplona.quartz.nip17Dm.files.encryption.AESGCM
import junit.framework.TestCase.assertEquals
+import okhttp3.OkHttpClient
import okhttp3.Request
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
-class DMDecryptionTest {
- val okHttp = HttpClientManager.getHttpClient(false)
+class DMFileDecryptionTest {
+ // Key cache to download and decrypt encrypted files before caching them.
+ val keyCache = EncryptionKeyCache()
val url = "https://cdn.satellite.earth/812fd4cf9d4d4b59c141ecd6a6c08c7571b5872237ad6477916cb2d119b5cacd"
val cipher =
@@ -44,7 +47,13 @@ class DMDecryptionTest {
@Test
fun runDownloadAndDecryptVideo() {
- HttpClientManager.addCipherToCache(url, cipher, "video/mp4")
+ val client =
+ OkHttpClient
+ .Builder()
+ .addNetworkInterceptor(EncryptedBlobInterceptor(keyCache))
+ .build()
+
+ keyCache.add(url, cipher, "video/mp4")
val request =
Request
@@ -54,7 +63,7 @@ class DMDecryptionTest {
.get()
.build()
- okHttp.newCall(request).execute().use {
+ client.newCall(request).execute().use {
assertEquals(decryptedSize, it.body.bytes().size)
assertEquals(expectedMimeType, it.body.contentType().toString())
}
diff --git a/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/ImageUploadTesting.kt b/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/ImageUploadTesting.kt
index bf76e2ab6..90eca1f85 100644
--- a/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/ImageUploadTesting.kt
+++ b/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/ImageUploadTesting.kt
@@ -26,6 +26,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.AccountSettings
+import com.vitorpamplona.amethyst.service.okhttp.DefaultContentTypeInterceptor
import com.vitorpamplona.amethyst.service.uploads.FileHeader
import com.vitorpamplona.amethyst.service.uploads.ImageDownloader
import com.vitorpamplona.amethyst.service.uploads.blossom.BlossomUploader
@@ -43,6 +44,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.runBlocking
+import okhttp3.OkHttpClient
import org.junit.Assert
import org.junit.Ignore
import org.junit.Test
@@ -52,11 +54,21 @@ import kotlin.random.Random
@RunWith(AndroidJUnit4::class)
class ImageUploadTesting {
- val account =
- Account(
- AccountSettings(KeyPair()),
- scope = CoroutineScope(Dispatchers.IO + SupervisorJob()),
- )
+ companion object {
+ val account =
+ Account(
+ AccountSettings(KeyPair()),
+ scope = CoroutineScope(Dispatchers.IO + SupervisorJob()),
+ )
+ }
+
+ val client =
+ OkHttpClient
+ .Builder()
+ .followRedirects(true)
+ .followSslRedirects(true)
+ .addInterceptor(DefaultContentTypeInterceptor("Amethyst/${BuildConfig.VERSION_NAME}"))
+ .build()
private suspend fun getBitmap(): ByteArray {
val bitmap = Bitmap.createBitmap(200, 300, Bitmap.Config.ARGB_8888)
@@ -94,7 +106,7 @@ class ImageUploadTesting {
alt = null,
sensitiveContent = null,
serverBaseUrl = server.baseUrl,
- forceProxy = { false },
+ okHttpClient = { client },
httpAuth = account::createBlossomUploadAuth,
context = InstrumentationRegistry.getInstrumentation().targetContext,
)
@@ -105,7 +117,7 @@ class ImageUploadTesting {
assertEquals("${server.baseUrl}/$initialHash", result.url?.removeSuffix(".png"))
val imageData: ByteArray =
- ImageDownloader().waitAndGetImage(result.url!!, false)?.bytes
+ ImageDownloader().waitAndGetImage(result.url!!, { client })?.bytes
?: run {
fail("${server.name}: Should not be null")
return
@@ -120,7 +132,7 @@ class ImageUploadTesting {
ServerInfoRetriever()
.loadInfo(
server.baseUrl,
- false,
+ { client },
)
val paylod = getBitmap()
@@ -134,7 +146,7 @@ class ImageUploadTesting {
alt = null,
sensitiveContent = null,
server = serverInfo,
- forceProxy = { false },
+ okHttpClient = { client },
onProgress = {},
httpAuth = account::createHTTPAuthorization,
context = InstrumentationRegistry.getInstrumentation().targetContext,
@@ -148,7 +160,7 @@ class ImageUploadTesting {
Assert.assertTrue("${server.name}: Invalid result url", url.startsWith("http"))
val imageData: ByteArray =
- ImageDownloader().waitAndGetImage(url, false)?.bytes
+ ImageDownloader().waitAndGetImage(url, { client })?.bytes
?: run {
fail("${server.name}: Should not be null")
return
@@ -255,6 +267,7 @@ class ImageUploadTesting {
}
@Test()
+ @Ignore("Not Working anymore/ Timeout")
fun testNostrCheckBlossom() =
runBlocking {
testBase(ServerName("nostrcheck", "https://cdn.nostrcheck.me", ServerType.Blossom))
diff --git a/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/OkHttpOtsTest.kt b/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/OkHttpOtsTest.kt
index 956ae15ad..d6503b45b 100644
--- a/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/OkHttpOtsTest.kt
+++ b/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/OkHttpOtsTest.kt
@@ -21,17 +21,17 @@
package com.vitorpamplona.amethyst
import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.vitorpamplona.amethyst.service.ots.OkHttpBlockstreamExplorer
+import com.vitorpamplona.amethyst.service.ots.OkHttpBitcoinExplorer
import com.vitorpamplona.amethyst.service.ots.OkHttpCalendarBuilder
+import com.vitorpamplona.amethyst.service.ots.OtsBlockHeightCache
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
import com.vitorpamplona.quartz.nip01Core.jackson.EventMapper
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal
import com.vitorpamplona.quartz.nip03Timestamp.OtsEvent
import com.vitorpamplona.quartz.nip03Timestamp.OtsResolver
-import com.vitorpamplona.quartz.nip03Timestamp.ots.OpenTimestamps
import junit.framework.TestCase.assertEquals
+import okhttp3.OkHttpClient
import org.junit.Assert
-import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import java.util.concurrent.CountDownLatch
@@ -44,31 +44,37 @@ class OkHttpOtsTest {
val otsPendingEvent = "{\"id\":\"12fa15ad4b4cf9dc5940389325b69b93c5c1f59c049c701ee669b275299fdaf1\",\"pubkey\":\"dcaa6c8a2f47b6fef4a34b20e8843c59dbe7c5f07a402338c09fd147dd01d22b\",\"created_at\":1708877521,\"kind\":1040,\"tags\":[[\"e\",\"a8634f5368e17789e8fb3836cad0e4b9fe4f2288afafc28723c04bebc68a18d6\"],[\"alt\",\"Opentimestamps Attestation\"]],\"content\":\"AE9wZW5UaW1lc3RhbXBzAABQcm9vZgC/ieLohOiSlAEIqGNPU2jhd4no+zg2ytDkuf5PIoivr8KHI8BL68aKGNbwELidvzr0usf55CkpKf6OABQI//AQK3sWd2tq+7KO8YNJIARJugjxBGXbZtPwCL0H4/7GL5+SAIPf4w0u+QyOLCtodHRwczovL2JvYi5idGMuY2FsZW5kYXIub3BlbnRpbWVzdGFtcHMub3Jn//AQPDZsJgN1TnJXoUzlsgo93wjwIIfBc7LUqkCbC1BLZRZ+6LXztK50UdH5xe7fn40bupkrCPEEZdtm0/AI0CADXN5ZIncAg9/jDS75DI4uLWh0dHBzOi8vYWxpY2UuYnRjLmNhbGVuZGFyLm9wZW50aW1lc3RhbXBzLm9yZ/AQcELcSrE04cuGKlZQf2LeVwjwILUDSf9vK2GaefKTpn/LV2oUsQaA5WbqaP3C+1ZxQfRNCPEEZdtm0/AIbCtb+yRXFqUAg9/jDS75DI4pKGh0dHBzOi8vZmlubmV5LmNhbGVuZGFyLmV0ZXJuaXR5d2FsbC5jb20=\",\"sig\":\"f6854c0228c15c08aeb70bbabe9ed87bbb7289fab31b13cabac15138bb71179553e06080b83f4a813fbdaf614f63293beea3fc73fe865da6551193fa4d38de04\"}"
val otsEvent2Digest = "a8634f5368e17789e8fb3836cad0e4b9fe4f2288afafc28723c04bebc68a18d6"
+ val otsCache = OtsBlockHeightCache()
- @Before
- fun setup() {
- OtsResolver.ots = OpenTimestamps(OkHttpBlockstreamExplorer(forceProxy = { false }), OkHttpCalendarBuilder(forceProxy = { false }))
- }
+ val resolver =
+ OtsResolver(
+ OkHttpBitcoinExplorer(
+ OkHttpBitcoinExplorer.MEMPOOL_API_URL,
+ client = OkHttpClient.Builder().build(),
+ otsCache,
+ ),
+ OkHttpCalendarBuilder { OkHttpClient.Builder().build() },
+ )
@Test
fun verifyNostrEvent() {
val ots = EventMapper.fromJson(otsEvent) as OtsEvent
- println(OtsResolver.info(ots.otsByteArray()))
- assertEquals(1707688818L, ots.verify())
+ println(resolver.info(ots.otsByteArray()))
+ assertEquals(1707688818L, ots.verify(resolver))
}
@Test
fun verifyNostrEvent2() {
val ots = EventMapper.fromJson(otsEvent2) as OtsEvent
- println(OtsResolver.info(ots.otsByteArray()))
- assertEquals(1706322179L, ots.verify())
+ println(resolver.info(ots.otsByteArray()))
+ assertEquals(1706322179L, ots.verify(resolver))
}
@Test
fun verifyNostrPendingEvent() {
val ots = EventMapper.fromJson(otsPendingEvent) as OtsEvent
- println(OtsResolver.info(ots.otsByteArray()))
- assertEquals(null, ots.verify())
+ println(resolver.info(ots.otsByteArray()))
+ assertEquals(null, ots.verify(resolver))
}
@Test
@@ -78,7 +84,7 @@ class OkHttpOtsTest {
val countDownLatch = CountDownLatch(1)
- val otsFile = OtsEvent.stamp(otsEvent2Digest)
+ val otsFile = OtsEvent.stamp(otsEvent2Digest, resolver)
signer.sign(OtsEvent.build(otsEvent2Digest, otsFile)) {
ots = it
@@ -88,9 +94,9 @@ class OkHttpOtsTest {
Assert.assertTrue(countDownLatch.await(1, TimeUnit.SECONDS))
println(ots!!.toJson())
- println(OtsResolver.info(ots!!.otsByteArray()))
+ println(resolver.info(ots.otsByteArray()))
// Should not be valid because we need to wait for confirmations
- assertEquals(null, ots!!.verify())
+ assertEquals(null, ots.verify(resolver))
}
}
diff --git a/amethyst/src/androidTest/res/values/strings.xml b/amethyst/src/androidTest/res/values/strings.xml
index 024194479..7e708a999 100644
--- a/amethyst/src/androidTest/res/values/strings.xml
+++ b/amethyst/src/androidTest/res/values/strings.xml
@@ -1,5 +1,5 @@
- Amethyst
+ Amethyst
Amy Debug
Amy Benchmark
\ No newline at end of file
diff --git a/amethyst/src/fdroid/java/com/vitorpamplona/amethyst/service/notifications/PushMessageReceiver.kt b/amethyst/src/fdroid/java/com/vitorpamplona/amethyst/service/notifications/PushMessageReceiver.kt
index 20d697e8b..837138d01 100644
--- a/amethyst/src/fdroid/java/com/vitorpamplona/amethyst/service/notifications/PushMessageReceiver.kt
+++ b/amethyst/src/fdroid/java/com/vitorpamplona/amethyst/service/notifications/PushMessageReceiver.kt
@@ -89,7 +89,9 @@ class PushMessageReceiver : MessagingReceiver() {
Log.d(TAG, "New endpoint provided:- $endpoint for Instance: $instance ${pushHandler.getSavedEndpoint()} $sanitizedEndpoint")
pushHandler.setEndpoint(sanitizedEndpoint)
scope.launch(Dispatchers.IO) {
- RegisterAccounts(LocalPreferences.allSavedAccounts()).go(sanitizedEndpoint)
+ PushNotificationUtils.checkAndInit(sanitizedEndpoint, LocalPreferences.allSavedAccounts()) {
+ Amethyst.instance.okHttpClients.getHttpClient(Amethyst.instance.torManager.isSocksReady())
+ }
notificationManager().getOrCreateZapChannel(appContext)
notificationManager().getOrCreateDMChannel(appContext)
}
diff --git a/amethyst/src/fdroid/java/com/vitorpamplona/amethyst/service/notifications/PushNotificationUtils.kt b/amethyst/src/fdroid/java/com/vitorpamplona/amethyst/service/notifications/PushNotificationUtils.kt
index 21ca41b1f..6deb400c6 100644
--- a/amethyst/src/fdroid/java/com/vitorpamplona/amethyst/service/notifications/PushNotificationUtils.kt
+++ b/amethyst/src/fdroid/java/com/vitorpamplona/amethyst/service/notifications/PushNotificationUtils.kt
@@ -20,28 +20,53 @@
*/
package com.vitorpamplona.amethyst.service.notifications
-import android.util.Log
import com.vitorpamplona.amethyst.AccountInfo
-import kotlinx.coroutines.CancellationException
+import com.vitorpamplona.amethyst.service.retryIfException
+import kotlinx.coroutines.Dispatchers
+import okhttp3.OkHttpClient
object PushNotificationUtils {
- var hasInit: Boolean = false
+ var lastToken: String? = null
+ var hasInit: List? = null
+
private val pushHandler = PushDistributorHandler
- suspend fun init(accounts: List) {
- if (hasInit) {
- return
- }
- try {
- if (pushHandler.savedDistributorExists()) {
- val currentDistributor = PushDistributorHandler.getSavedDistributor()
- PushDistributorHandler.saveDistributor(currentDistributor)
+ suspend fun checkAndInit(
+ accounts: List,
+ okHttpClient: (String) -> OkHttpClient,
+ ) = with(Dispatchers.IO) {
+ if (!pushHandler.savedDistributorExists()) return
- RegisterAccounts(accounts).go(pushHandler.getSavedEndpoint())
- }
- } catch (e: Exception) {
- if (e is CancellationException) throw e
- Log.d("Amethyst-OSSPushUtils", "Failed to get endpoint.")
+ val currentDistributor = PushDistributorHandler.getSavedDistributor()
+ PushDistributorHandler.saveDistributor(currentDistributor)
+ val token = pushHandler.getSavedEndpoint()
+
+ if (hasInit?.equals(accounts) == true && lastToken == token) {
+ return@with
}
+
+ registerToken(token, accounts, okHttpClient)
+ }
+
+ suspend fun checkAndInit(
+ token: String,
+ accounts: List,
+ okHttpClient: (String) -> OkHttpClient,
+ ) = with(Dispatchers.IO) {
+ // initializes if the accounts are different or if the token has changed
+ if (hasInit?.equals(accounts) == true && lastToken == token) {
+ return@with
+ }
+ registerToken(token, accounts, okHttpClient)
+ }
+
+ private suspend fun registerToken(
+ token: String,
+ accounts: List,
+ okHttpClient: (String) -> OkHttpClient,
+ ) = retryIfException("RegisterAccounts") {
+ RegisterAccounts(accounts, okHttpClient).go(token)
+ lastToken = token
+ hasInit = accounts.toList()
}
}
diff --git a/amethyst/src/main/AndroidManifest.xml b/amethyst/src/main/AndroidManifest.xml
index af1a6163c..7daf6a19e 100644
--- a/amethyst/src/main/AndroidManifest.xml
+++ b/amethyst/src/main/AndroidManifest.xml
@@ -66,7 +66,7 @@
+ Log.e("AmethystCoroutine", "Caught exception: ${throwable.message}", throwable)
+ }
- // Service Manager is only active when the activity is active.
- val serviceManager = ServiceManager(client, applicationIOScope)
+ val applicationIOScope = CoroutineScope(Dispatchers.IO + SupervisorJob() + exceptionHandler)
+
+ // Key cache to download and decrypt encrypted files before caching them.
+ val keyCache = EncryptionKeyCache()
+
+ // App services that should be run as soon as there are subscribers to their flows
val locationManager = LocationState(this, applicationIOScope)
+ val torManager = TorManager(this, applicationIOScope)
+ val connManager = ConnectivityManager(this, applicationIOScope)
+ // Service that will run at all times.
val pokeyReceiver = PokeyReceiver()
+ val okHttpClients =
+ DualHttpClientManager(
+ userAgent = appAgent,
+ proxyPortProvider = torManager.activePortOrNull,
+ isMobileDataProvider = connManager.isMobileOrNull,
+ keyCache = keyCache,
+ scope = applicationIOScope,
+ )
+
+ val factory =
+ OkHttpWebSocket.BuilderFactory { _, useProxy ->
+ okHttpClients.getHttpClient(useProxy)
+ }
+
+ val client: NostrClient = NostrClient(factory)
+
+ val serviceManager = ServiceManager(client, applicationIOScope)
+
+ val nip95cache: File by lazy { Nip95CacheFactory.new(this) }
+ val videoCache: VideoCache by lazy { VideoCacheFactory.new(this) }
+ val diskCache: DiskCache by lazy { ImageCacheFactory.newDisk(this) }
+ val memoryCache: MemoryCache by lazy { ImageCacheFactory.newMemory(this) }
+
+ val otsVerifCache by lazy { VerificationStateCache() }
+ val otsBlockHeightCache by lazy { OtsBlockHeightCache() }
+
+ override fun onCreate() {
+ super.onCreate()
+ Log.d("AmethystApp", "onCreate $this")
+
+ instance = this
+
+ if (isDebug()) {
+ Logging.setup()
+ }
+
+ // initializes diskcache on an IO thread.
+ applicationIOScope.launch { videoCache }
+
+ // registers to receive events
+ pokeyReceiver.register(this)
+ }
+
override fun onTerminate() {
super.onTerminate()
- unregisterReceiver(pokeyReceiver)
- applicationIOScope.cancel()
- }
+ Log.d("AmethystApp", "onTerminate $this")
- fun nip95cache() = safeCacheDir.resolve("NIP95")
-
- val videoCache: VideoCache by lazy {
- val newCache = VideoCache()
- runBlocking {
- newCache.initFileCache(
- this@Amethyst,
- safeCacheDir.resolve("exoplayer"),
- )
- }
- newCache
- }
-
- val coilCache: DiskCache by lazy {
- DiskCache
- .Builder()
- .directory(safeCacheDir.resolve("image_cache").toOkioPath())
- .maxSizePercent(0.2)
- .maximumMaxSizeBytes(1024 * 1024 * 1024) // 1GB
- .build()
- }
-
- val memoryCache: MemoryCache by lazy {
- MemoryCache
- .Builder()
- .maxSizePercent(this)
- .build()
+ pokeyReceiver.unregister(this)
+ applicationIOScope.cancel("Application onTerminate $this")
}
fun contentResolverFn(): ContentResolver = contentResolver
- override fun onCreate() {
- super.onCreate()
+ fun isDebug() = BuildConfig.DEBUG || BuildConfig.BUILD_TYPE == "benchmark"
- instance = this
-
- HttpClientManager.setDefaultUserAgent("Amethyst/${BuildConfig.VERSION_NAME}")
-
- if (BuildConfig.DEBUG || BuildConfig.BUILD_TYPE == "benchmark") {
- StrictMode.setThreadPolicy(
- ThreadPolicy
- .Builder()
- .detectAll()
- .penaltyLog()
- .build(),
- )
- StrictMode.setVmPolicy(
- VmPolicy
- .Builder()
- .detectAll()
- .penaltyLog()
- .build(),
- )
- Looper.getMainLooper().setMessageLogging(LogMonitor())
- ChoreographerHelper.start()
+ fun setImageLoader(shouldUseTor: Boolean?) =
+ ImageLoaderSetup.setup(this, diskCache, memoryCache, isDebug()) {
+ shouldUseTor?.let { okHttpClients.getHttpClient(it) } ?: okHttpClients.getHttpClient(false)
}
- GlobalScope.launch(Dispatchers.IO) {
- val (value, elapsed) =
- measureTimedValue {
- // initializes the video cache in a thread
- videoCache
- }
- Log.d("Rendering Metrics", "VideoCache initialized in $elapsed")
- }
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
- registerReceiver(
- pokeyReceiver,
- IntentFilter(PokeyReceiver.POKEY_ACTION),
- RECEIVER_EXPORTED,
- )
- } else {
- @Suppress("UnspecifiedRegisterReceiverFlag")
- registerReceiver(
- pokeyReceiver,
- IntentFilter(PokeyReceiver.POKEY_ACTION),
- )
- }
- }
-
- fun imageLoaderBuilder(): ImageLoader.Builder =
- ImageLoader
- .Builder(this)
- .diskCache { coilCache }
- .memoryCache { memoryCache }
-
fun encryptedStorage(npub: String? = null): EncryptedSharedPreferences = EncryptedStorage.preferences(instance, npub)
/**
@@ -167,32 +138,16 @@ class Amethyst : Application() {
*
* @param level the memory-related event that was raised.
*/
- @OptIn(DelicateCoroutinesApi::class)
override fun onTrimMemory(level: Int) {
super.onTrimMemory(level)
- println("Trim Memory $level")
- GlobalScope.launch(Dispatchers.Default) {
- println("Trim Memory Inside $level")
+ Log.d("AmethystApp", "onTrimMemory $level")
+ applicationIOScope.launch(Dispatchers.Default) {
serviceManager.trimMemory()
}
}
- fun createIntent(callbackUri: String): PendingIntent =
- PendingIntent.getActivity(
- this,
- 0,
- Intent(Intent.ACTION_VIEW, callbackUri.toUri(), this, MainActivity::class.java),
- PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
- )
-
companion object {
lateinit var instance: Amethyst
private set
}
}
-
-internal val Context.safeCacheDir: File
- get() {
- val cacheDir = checkNotNull(cacheDir) { "cacheDir == null" }
- return cacheDir.apply { mkdirs() }
- }
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/DebugUtils.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/DebugUtils.kt
index e075cb8e6..f0001431c 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/DebugUtils.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/DebugUtils.kt
@@ -91,7 +91,7 @@ fun debugState(context: Context) {
Log.d(
"STATE DUMP",
- "Image Disk Cache ${(Amethyst.instance.coilCache.size) / (1024 * 1024)}/${(Amethyst.instance.coilCache.maxSize) / (1024 * 1024)} MB",
+ "Image Disk Cache ${(Amethyst.instance.diskCache.size) / (1024 * 1024)}/${(Amethyst.instance.diskCache.maxSize) / (1024 * 1024)} MB",
)
Log.d(
"STATE DUMP",
diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt
index 793881ef2..6ef2fe673 100644
--- a/amethyst/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt
+++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/LocalPreferences.kt
@@ -25,6 +25,7 @@ import android.content.Context
import android.content.SharedPreferences
import android.util.Log
import androidx.compose.runtime.Immutable
+import androidx.core.content.edit
import com.fasterxml.jackson.module.kotlin.readValue
import com.vitorpamplona.amethyst.model.AccountLanguagePreferencesInternal
import com.vitorpamplona.amethyst.model.AccountReactionPreferencesInternal
@@ -151,13 +152,13 @@ object LocalPreferences {
if (info == null) {
currentAccount = null
withContext(Dispatchers.IO) {
- encryptedPreferences().edit().clear().apply()
+ encryptedPreferences().edit { clear() }
}
} else if (currentAccount != info.npub) {
currentAccount = info.npub
if (!info.isTransient) {
withContext(Dispatchers.IO) {
- encryptedPreferences().edit().apply { putString(PrefKeys.CURRENT_ACCOUNT, info.npub) }.apply()
+ encryptedPreferences().edit { putString(PrefKeys.CURRENT_ACCOUNT, info.npub) }
}
}
}
@@ -189,7 +190,7 @@ object LocalPreferences {
savedAccounts.emit(migrated)
- edit().apply { putString(PrefKeys.ALL_ACCOUNT_INFO, EventMapper.mapper.writeValueAsString(savedAccounts.value)) }.apply()
+ edit { putString(PrefKeys.ALL_ACCOUNT_INFO, EventMapper.mapper.writeValueAsString(savedAccounts.value)) }
}
}
}
@@ -206,13 +207,12 @@ object LocalPreferences {
savedAccounts.emit(accounts)
encryptedPreferences()
- .edit()
- .apply {
+ .edit {
putString(
PrefKeys.ALL_ACCOUNT_INFO,
EventMapper.mapper.writeValueAsString(accounts.filter { !it.isTransient }),
)
- }.apply()
+ }
}
}
@@ -280,7 +280,7 @@ object LocalPreferences {
suspend fun updatePrefsForLogout(accountInfo: AccountInfo) {
Log.d("LocalPreferences", "Saving to encrypted storage updatePrefsForLogout ${accountInfo.npub}")
withContext(Dispatchers.IO) {
- encryptedPreferences(accountInfo.npub).edit().clear().commit()
+ encryptedPreferences(accountInfo.npub).edit(commit = true) { clear() }
removeAccount(accountInfo)
deleteUserPreferenceFile(accountInfo.npub)
@@ -304,142 +304,140 @@ object LocalPreferences {
if (!settings.transientAccount) {
withContext(Dispatchers.IO) {
val prefs = encryptedPreferences(settings.keyPair.pubKey.toNpub())
- prefs
- .edit()
- .apply {
- putBoolean(PrefKeys.LOGIN_WITH_EXTERNAL_SIGNER, settings.externalSignerPackageName != null)
- if (settings.externalSignerPackageName != null) {
- remove(PrefKeys.NOSTR_PRIVKEY)
- putString(PrefKeys.SIGNER_PACKAGE_NAME, settings.externalSignerPackageName)
- } else {
- remove(PrefKeys.SIGNER_PACKAGE_NAME)
- settings.keyPair.privKey?.let { putString(PrefKeys.NOSTR_PRIVKEY, it.toHexKey()) }
- }
- settings.keyPair.pubKey.let { putString(PrefKeys.NOSTR_PUBKEY, it.toHexKey()) }
- putString(PrefKeys.RELAYS, EventMapper.mapper.writeValueAsString(settings.localRelays))
+ prefs.edit {
+ putBoolean(PrefKeys.LOGIN_WITH_EXTERNAL_SIGNER, settings.externalSignerPackageName != null)
+ if (settings.externalSignerPackageName != null) {
+ remove(PrefKeys.NOSTR_PRIVKEY)
+ putString(PrefKeys.SIGNER_PACKAGE_NAME, settings.externalSignerPackageName)
+ } else {
+ remove(PrefKeys.SIGNER_PACKAGE_NAME)
+ settings.keyPair.privKey?.let { putString(PrefKeys.NOSTR_PRIVKEY, it.toHexKey()) }
+ }
+ settings.keyPair.pubKey.let { putString(PrefKeys.NOSTR_PUBKEY, it.toHexKey()) }
+ putString(PrefKeys.RELAYS, EventMapper.mapper.writeValueAsString(settings.localRelays))
+ putString(
+ PrefKeys.DEFAULT_FILE_SERVER,
+ EventMapper.mapper.writeValueAsString(settings.defaultFileServer),
+ )
+ putString(PrefKeys.DEFAULT_HOME_FOLLOW_LIST, settings.defaultHomeFollowList.value)
+ putString(PrefKeys.DEFAULT_STORIES_FOLLOW_LIST, settings.defaultStoriesFollowList.value)
+ putString(
+ PrefKeys.DEFAULT_NOTIFICATION_FOLLOW_LIST,
+ settings.defaultNotificationFollowList.value,
+ )
+ putString(
+ PrefKeys.DEFAULT_DISCOVERY_FOLLOW_LIST,
+ settings.defaultDiscoveryFollowList.value,
+ )
+ putString(
+ PrefKeys.ZAP_PAYMENT_REQUEST_SERVER,
+ EventMapper.mapper.writeValueAsString(settings.zapPaymentRequest),
+ )
+ if (settings.backupContactList != null) {
putString(
- PrefKeys.DEFAULT_FILE_SERVER,
- EventMapper.mapper.writeValueAsString(settings.defaultFileServer),
+ PrefKeys.LATEST_CONTACT_LIST,
+ EventMapper.mapper.writeValueAsString(settings.backupContactList),
)
- putString(PrefKeys.DEFAULT_HOME_FOLLOW_LIST, settings.defaultHomeFollowList.value)
- putString(PrefKeys.DEFAULT_STORIES_FOLLOW_LIST, settings.defaultStoriesFollowList.value)
+ } else {
+ remove(PrefKeys.LATEST_CONTACT_LIST)
+ }
+
+ if (settings.backupUserMetadata != null) {
putString(
- PrefKeys.DEFAULT_NOTIFICATION_FOLLOW_LIST,
- settings.defaultNotificationFollowList.value,
+ PrefKeys.LATEST_USER_METADATA,
+ EventMapper.mapper.writeValueAsString(settings.backupUserMetadata),
)
+ } else {
+ remove(PrefKeys.LATEST_USER_METADATA)
+ }
+
+ if (settings.backupDMRelayList != null) {
putString(
- PrefKeys.DEFAULT_DISCOVERY_FOLLOW_LIST,
- settings.defaultDiscoveryFollowList.value,
+ PrefKeys.LATEST_DM_RELAY_LIST,
+ EventMapper.mapper.writeValueAsString(settings.backupDMRelayList),
)
+ } else {
+ remove(PrefKeys.LATEST_DM_RELAY_LIST)
+ }
+
+ if (settings.backupNIP65RelayList != null) {
putString(
- PrefKeys.ZAP_PAYMENT_REQUEST_SERVER,
- EventMapper.mapper.writeValueAsString(settings.zapPaymentRequest),
+ PrefKeys.LATEST_NIP65_RELAY_LIST,
+ EventMapper.mapper.writeValueAsString(settings.backupNIP65RelayList),
)
- if (settings.backupContactList != null) {
- putString(
- PrefKeys.LATEST_CONTACT_LIST,
- EventMapper.mapper.writeValueAsString(settings.backupContactList),
- )
- } else {
- remove(PrefKeys.LATEST_CONTACT_LIST)
- }
-
- if (settings.backupUserMetadata != null) {
- putString(
- PrefKeys.LATEST_USER_METADATA,
- EventMapper.mapper.writeValueAsString(settings.backupUserMetadata),
- )
- } else {
- remove(PrefKeys.LATEST_USER_METADATA)
- }
-
- if (settings.backupDMRelayList != null) {
- putString(
- PrefKeys.LATEST_DM_RELAY_LIST,
- EventMapper.mapper.writeValueAsString(settings.backupDMRelayList),
- )
- } else {
- remove(PrefKeys.LATEST_DM_RELAY_LIST)
- }
-
- if (settings.backupNIP65RelayList != null) {
- putString(
- PrefKeys.LATEST_NIP65_RELAY_LIST,
- EventMapper.mapper.writeValueAsString(settings.backupNIP65RelayList),
- )
- } else {
- remove(PrefKeys.LATEST_NIP65_RELAY_LIST)
- }
-
- if (settings.backupSearchRelayList != null) {
- putString(
- PrefKeys.LATEST_SEARCH_RELAY_LIST,
- EventMapper.mapper.writeValueAsString(settings.backupSearchRelayList),
- )
- } else {
- remove(PrefKeys.LATEST_SEARCH_RELAY_LIST)
- }
-
- if (settings.localRelayServers.isNotEmpty()) {
- putStringSet(PrefKeys.LOCAL_RELAY_SERVERS, settings.localRelayServers)
- } else {
- remove(PrefKeys.LOCAL_RELAY_SERVERS)
- }
-
- if (settings.backupMuteList != null) {
- putString(
- PrefKeys.LATEST_MUTE_LIST,
- EventMapper.mapper.writeValueAsString(settings.backupMuteList),
- )
- } else {
- remove(PrefKeys.LATEST_MUTE_LIST)
- }
-
- if (settings.backupPrivateHomeRelayList != null) {
- putString(
- PrefKeys.LATEST_PRIVATE_HOME_RELAY_LIST,
- EventMapper.mapper.writeValueAsString(settings.backupPrivateHomeRelayList),
- )
- } else {
- remove(PrefKeys.LATEST_PRIVATE_HOME_RELAY_LIST)
- }
-
- if (settings.backupAppSpecificData != null) {
- putString(
- PrefKeys.LATEST_APP_SPECIFIC_DATA,
- EventMapper.mapper.writeValueAsString(settings.backupAppSpecificData),
- )
- } else {
- remove(PrefKeys.LATEST_APP_SPECIFIC_DATA)
- }
-
- putBoolean(PrefKeys.HIDE_DELETE_REQUEST_DIALOG, settings.hideDeleteRequestDialog)
- putBoolean(PrefKeys.HIDE_NIP_17_WARNING_DIALOG, settings.hideNIP17WarningDialog)
- putBoolean(PrefKeys.HIDE_BLOCK_ALERT_DIALOG, settings.hideBlockAlertDialog)
-
- // migrating from previous design
- remove(PrefKeys.USE_PROXY)
- remove(PrefKeys.PROXY_PORT)
-
- putString(PrefKeys.TOR_SETTINGS, EventMapper.mapper.writeValueAsString(settings.torSettings.toSettings()))
-
- val regularMap =
- settings.lastReadPerRoute.value.mapValues {
- it.value.value
- }
+ } else {
+ remove(PrefKeys.LATEST_NIP65_RELAY_LIST)
+ }
+ if (settings.backupSearchRelayList != null) {
putString(
- PrefKeys.LAST_READ_PER_ROUTE,
- EventMapper.mapper.writeValueAsString(regularMap),
+ PrefKeys.LATEST_SEARCH_RELAY_LIST,
+ EventMapper.mapper.writeValueAsString(settings.backupSearchRelayList),
)
- putStringSet(PrefKeys.HAS_DONATED_IN_VERSION, settings.hasDonatedInVersion.value)
+ } else {
+ remove(PrefKeys.LATEST_SEARCH_RELAY_LIST)
+ }
+ if (settings.localRelayServers.isNotEmpty()) {
+ putStringSet(PrefKeys.LOCAL_RELAY_SERVERS, settings.localRelayServers)
+ } else {
+ remove(PrefKeys.LOCAL_RELAY_SERVERS)
+ }
+
+ if (settings.backupMuteList != null) {
putString(
- PrefKeys.PENDING_ATTESTATIONS,
- EventMapper.mapper.writeValueAsString(settings.pendingAttestations.value),
+ PrefKeys.LATEST_MUTE_LIST,
+ EventMapper.mapper.writeValueAsString(settings.backupMuteList),
)
- }.apply()
+ } else {
+ remove(PrefKeys.LATEST_MUTE_LIST)
+ }
+
+ if (settings.backupPrivateHomeRelayList != null) {
+ putString(
+ PrefKeys.LATEST_PRIVATE_HOME_RELAY_LIST,
+ EventMapper.mapper.writeValueAsString(settings.backupPrivateHomeRelayList),
+ )
+ } else {
+ remove(PrefKeys.LATEST_PRIVATE_HOME_RELAY_LIST)
+ }
+
+ if (settings.backupAppSpecificData != null) {
+ putString(
+ PrefKeys.LATEST_APP_SPECIFIC_DATA,
+ EventMapper.mapper.writeValueAsString(settings.backupAppSpecificData),
+ )
+ } else {
+ remove(PrefKeys.LATEST_APP_SPECIFIC_DATA)
+ }
+
+ putBoolean(PrefKeys.HIDE_DELETE_REQUEST_DIALOG, settings.hideDeleteRequestDialog)
+ putBoolean(PrefKeys.HIDE_NIP_17_WARNING_DIALOG, settings.hideNIP17WarningDialog)
+ putBoolean(PrefKeys.HIDE_BLOCK_ALERT_DIALOG, settings.hideBlockAlertDialog)
+
+ // migrating from previous design
+ remove(PrefKeys.USE_PROXY)
+ remove(PrefKeys.PROXY_PORT)
+
+ putString(PrefKeys.TOR_SETTINGS, EventMapper.mapper.writeValueAsString(settings.torSettings.toSettings()))
+
+ val regularMap =
+ settings.lastReadPerRoute.value.mapValues {
+ it.value.value
+ }
+
+ putString(
+ PrefKeys.LAST_READ_PER_ROUTE,
+ EventMapper.mapper.writeValueAsString(regularMap),
+ )
+ putStringSet(PrefKeys.HAS_DONATED_IN_VERSION, settings.hasDonatedInVersion.value)
+
+ putString(
+ PrefKeys.PENDING_ATTESTATIONS,
+ EventMapper.mapper.writeValueAsString(settings.pendingAttestations.value),
+ )
+ }
}
}
Log.d("LocalPreferences", "Saved to encrypted storage")
@@ -501,161 +499,163 @@ object LocalPreferences {
private suspend fun innerLoadCurrentAccountFromEncryptedStorage(npub: String?): AccountSettings? {
Log.d("LocalPreferences", "Load account from file $npub")
+ val result =
+ withContext(Dispatchers.IO) {
+ checkNotInMainThread()
- return withContext(Dispatchers.IO) {
- checkNotInMainThread()
+ return@withContext with(encryptedPreferences(npub)) {
+ val privKey = getString(PrefKeys.NOSTR_PRIVKEY, null)
+ val pubKey = getString(PrefKeys.NOSTR_PUBKEY, null) ?: return@with null
+ val externalSignerPackageName =
+ getString(PrefKeys.SIGNER_PACKAGE_NAME, null)
+ ?: if (getBoolean(PrefKeys.LOGIN_WITH_EXTERNAL_SIGNER, false)) "com.greenart7c3.nostrsigner" else null
- return@withContext with(encryptedPreferences(npub)) {
- val privKey = getString(PrefKeys.NOSTR_PRIVKEY, null)
- val pubKey = getString(PrefKeys.NOSTR_PUBKEY, null) ?: return@with null
- val externalSignerPackageName =
- getString(PrefKeys.SIGNER_PACKAGE_NAME, null)
- ?: if (getBoolean(PrefKeys.LOGIN_WITH_EXTERNAL_SIGNER, false)) "com.greenart7c3.nostrsigner" else null
+ val defaultHomeFollowList =
+ getString(PrefKeys.DEFAULT_HOME_FOLLOW_LIST, null) ?: KIND3_FOLLOWS
+ val defaultStoriesFollowList =
+ getString(PrefKeys.DEFAULT_STORIES_FOLLOW_LIST, null) ?: GLOBAL_FOLLOWS
+ val defaultNotificationFollowList =
+ getString(PrefKeys.DEFAULT_NOTIFICATION_FOLLOW_LIST, null) ?: GLOBAL_FOLLOWS
+ val defaultDiscoveryFollowList =
+ getString(PrefKeys.DEFAULT_DISCOVERY_FOLLOW_LIST, null) ?: GLOBAL_FOLLOWS
- val defaultHomeFollowList =
- getString(PrefKeys.DEFAULT_HOME_FOLLOW_LIST, null) ?: KIND3_FOLLOWS
- val defaultStoriesFollowList =
- getString(PrefKeys.DEFAULT_STORIES_FOLLOW_LIST, null) ?: GLOBAL_FOLLOWS
- val defaultNotificationFollowList =
- getString(PrefKeys.DEFAULT_NOTIFICATION_FOLLOW_LIST, null) ?: GLOBAL_FOLLOWS
- val defaultDiscoveryFollowList =
- getString(PrefKeys.DEFAULT_DISCOVERY_FOLLOW_LIST, null) ?: GLOBAL_FOLLOWS
+ val defaultZapType =
+ getString(PrefKeys.DEFAULT_ZAPTYPE, "")?.let { serverName ->
+ LnZapEvent.ZapType.entries.firstOrNull { it.name == serverName }
+ } ?: LnZapEvent.ZapType.PUBLIC
- val defaultZapType =
- getString(PrefKeys.DEFAULT_ZAPTYPE, "")?.let { serverName ->
- LnZapEvent.ZapType.entries.firstOrNull { it.name == serverName }
- } ?: LnZapEvent.ZapType.PUBLIC
+ val localRelays = parseOrNull>(PrefKeys.RELAYS) ?: emptySet()
- val localRelays = parseOrNull>(PrefKeys.RELAYS) ?: emptySet()
+ val zapPaymentRequestServer = parseOrNull(PrefKeys.ZAP_PAYMENT_REQUEST_SERVER)
+ val defaultFileServer = parseOrNull(PrefKeys.DEFAULT_FILE_SERVER) ?: DEFAULT_MEDIA_SERVERS[0]
- val zapPaymentRequestServer = parseOrNull(PrefKeys.ZAP_PAYMENT_REQUEST_SERVER)
- val defaultFileServer = parseOrNull(PrefKeys.DEFAULT_FILE_SERVER) ?: DEFAULT_MEDIA_SERVERS[0]
+ val pendingAttestations = parseOrNull