mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-08-08 06:46:11 +00:00
Merge branch 'main' of https://github.com/permissionlesstech/bitchat-android
This commit is contained in:
commit
8971c538e9
@ -70,6 +70,7 @@ import com.bitchat.android.nostr.NostrProofOfWork
|
||||
import com.bitchat.android.nostr.PoWPreferenceManager
|
||||
import com.bitchat.android.ui.theme.BitchatMotion
|
||||
import com.bitchat.android.ui.theme.LocalBitchatPalette
|
||||
import com.bitchat.android.util.ShareableApkVariant
|
||||
import com.bitchat.android.util.UniversalApkManager
|
||||
|
||||
/**
|
||||
@ -512,10 +513,13 @@ fun AboutSheet(
|
||||
is ApkPreparationStatus.Loading -> stringResource(R.string.checking)
|
||||
is ApkPreparationStatus.NotDownloaded -> stringResource(R.string.prepare_apk_status_not_downloaded)
|
||||
is ApkPreparationStatus.Ready -> {
|
||||
val source = if (status.source == UniversalApkManager.ApkSource.INSTALLED) {
|
||||
stringResource(R.string.prepare_apk_source_installed)
|
||||
} else {
|
||||
stringResource(R.string.prepare_apk_source_github)
|
||||
val source = when {
|
||||
status.source == UniversalApkManager.ApkSource.GITHUB ->
|
||||
stringResource(R.string.prepare_apk_source_github)
|
||||
status.variant == ShareableApkVariant.ARM64 ->
|
||||
stringResource(R.string.prepare_apk_source_installed_arm64)
|
||||
else ->
|
||||
stringResource(R.string.prepare_apk_source_installed)
|
||||
}
|
||||
stringResource(R.string.prepare_apk_status_ready) +
|
||||
" • ${status.version} • ${status.sizeMB} MB\n$source"
|
||||
@ -545,14 +549,36 @@ fun AboutSheet(
|
||||
)
|
||||
}
|
||||
is ApkPreparationStatus.Ready -> {
|
||||
if (apkStatus.source == UniversalApkManager.ApkSource.GITHUB) {
|
||||
if (apkStatus.variant == ShareableApkVariant.ARM64) {
|
||||
TextButton(
|
||||
onClick = {
|
||||
apkViewModel.onEvent(
|
||||
ApkUiEvent.DownloadUniversalClicked
|
||||
)
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.CloudDownload,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(18.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Text(
|
||||
stringResource(
|
||||
R.string.prepare_apk_get_universal
|
||||
)
|
||||
)
|
||||
}
|
||||
} else if (apkStatus.source == UniversalApkManager.ApkSource.GITHUB) {
|
||||
androidx.compose.material3.IconButton(
|
||||
onClick = { apkViewModel.onEvent(ApkUiEvent.DeleteClicked) },
|
||||
modifier = Modifier.size(32.dp)
|
||||
modifier = Modifier.size(48.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Delete,
|
||||
contentDescription = "Delete",
|
||||
contentDescription = stringResource(
|
||||
R.string.prepare_apk_delete_confirm
|
||||
),
|
||||
tint = colorScheme.error,
|
||||
modifier = Modifier.size(20.dp)
|
||||
)
|
||||
@ -562,11 +588,13 @@ fun AboutSheet(
|
||||
is ApkPreparationStatus.UpdateAvailable -> {
|
||||
androidx.compose.material3.IconButton(
|
||||
onClick = { apkViewModel.onEvent(ApkUiEvent.DeleteClicked) },
|
||||
modifier = Modifier.size(32.dp)
|
||||
modifier = Modifier.size(48.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Delete,
|
||||
contentDescription = "Delete",
|
||||
contentDescription = stringResource(
|
||||
R.string.prepare_apk_delete_confirm
|
||||
),
|
||||
tint = colorScheme.error,
|
||||
modifier = Modifier.size(20.dp)
|
||||
)
|
||||
|
||||
@ -7,6 +7,7 @@ import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.bitchat.android.R
|
||||
import com.bitchat.android.util.ApkDownloader
|
||||
import com.bitchat.android.util.ShareableApkVariant
|
||||
import com.bitchat.android.util.UniversalApkManager
|
||||
import com.bitchat.android.util.WorkManagerApkDownloader
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@ -27,7 +28,8 @@ sealed class ApkPreparationStatus {
|
||||
data class Ready(
|
||||
val version: String,
|
||||
val sizeMB: Int,
|
||||
val source: UniversalApkManager.ApkSource
|
||||
val source: UniversalApkManager.ApkSource,
|
||||
val variant: ShareableApkVariant
|
||||
) : ApkPreparationStatus()
|
||||
data class UpdateAvailable(
|
||||
val currentVersion: String,
|
||||
@ -52,6 +54,7 @@ data class ApkUiState(
|
||||
sealed class ApkUiEvent {
|
||||
object CheckStatus : ApkUiEvent()
|
||||
object PrepareRowClicked : ApkUiEvent()
|
||||
object DownloadUniversalClicked : ApkUiEvent()
|
||||
object ConfirmDownload : ApkUiEvent()
|
||||
object DismissPrepareDialog : ApkUiEvent()
|
||||
object DeleteClicked : ApkUiEvent()
|
||||
@ -99,6 +102,7 @@ class ApkDownloadViewModel(application: Application) : AndroidViewModel(applicat
|
||||
when (event) {
|
||||
is ApkUiEvent.CheckStatus -> checkStatus()
|
||||
is ApkUiEvent.PrepareRowClicked -> onPrepareRowClicked()
|
||||
is ApkUiEvent.DownloadUniversalClicked -> onDownloadUniversalClicked()
|
||||
is ApkUiEvent.ConfirmDownload -> onConfirmDownload()
|
||||
is ApkUiEvent.DismissPrepareDialog -> _state.update { it.copy(showPrepareDialog = false) }
|
||||
is ApkUiEvent.DeleteClicked -> _state.update { it.copy(showDeleteDialog = true) }
|
||||
@ -131,6 +135,15 @@ class ApkDownloadViewModel(application: Application) : AndroidViewModel(applicat
|
||||
startDownload()
|
||||
}
|
||||
|
||||
private fun onDownloadUniversalClicked() {
|
||||
val status = _state.value.apkStatus
|
||||
if (status is ApkPreparationStatus.Ready &&
|
||||
status.variant == ShareableApkVariant.ARM64
|
||||
) {
|
||||
_state.update { it.copy(showPrepareDialog = true) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun onConfirmDelete() {
|
||||
_state.update { it.copy(showDeleteDialog = false) }
|
||||
downloader.cancelDownload()
|
||||
@ -234,26 +247,47 @@ class ApkDownloadViewModel(application: Application) : AndroidViewModel(applicat
|
||||
_state.update {
|
||||
it.copy(
|
||||
apkStatus = ApkPreparationStatus.Ready(
|
||||
version = downloadState.version,
|
||||
sizeMB = downloadState.sizeMB,
|
||||
source = info?.source ?: UniversalApkManager.ApkSource.GITHUB
|
||||
version = info?.version ?: downloadState.version,
|
||||
sizeMB = info?.let { cached ->
|
||||
(cached.size / 1024 / 1024).toInt()
|
||||
} ?: downloadState.sizeMB,
|
||||
source = info?.source ?: UniversalApkManager.ApkSource.GITHUB,
|
||||
variant = info?.variant ?: ShareableApkVariant.UNIVERSAL
|
||||
),
|
||||
downloadProgress = 100
|
||||
)
|
||||
}
|
||||
}
|
||||
is ApkDownloader.DownloadState.Failed -> {
|
||||
_state.update {
|
||||
if (downloadState.resumablePercent != null) {
|
||||
val localArm64 = apkManager.getCachedApkInfo()
|
||||
?.takeIf { it.variant == ShareableApkVariant.ARM64 }
|
||||
if (localArm64 != null) {
|
||||
_state.update {
|
||||
it.copy(
|
||||
apkStatus = ApkPreparationStatus.Resumable(
|
||||
progressPercent = downloadState.resumablePercent,
|
||||
message = downloadState.message
|
||||
),
|
||||
downloadProgress = downloadState.resumablePercent
|
||||
apkStatus = ApkPreparationStatus.Ready(
|
||||
version = localArm64.version,
|
||||
sizeMB = (localArm64.size / 1024 / 1024).toInt(),
|
||||
source = localArm64.source,
|
||||
variant = localArm64.variant
|
||||
)
|
||||
)
|
||||
} else {
|
||||
it.copy(apkStatus = ApkPreparationStatus.Error(downloadState.message))
|
||||
}
|
||||
_effect.send(ApkUiEffect.ShowToast(downloadState.message))
|
||||
} else {
|
||||
_state.update {
|
||||
if (downloadState.resumablePercent != null) {
|
||||
it.copy(
|
||||
apkStatus = ApkPreparationStatus.Resumable(
|
||||
progressPercent = downloadState.resumablePercent,
|
||||
message = downloadState.message
|
||||
),
|
||||
downloadProgress = downloadState.resumablePercent
|
||||
)
|
||||
} else {
|
||||
it.copy(
|
||||
apkStatus = ApkPreparationStatus.Error(downloadState.message)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -295,7 +329,8 @@ class ApkDownloadViewModel(application: Application) : AndroidViewModel(applicat
|
||||
ApkPreparationStatus.Ready(
|
||||
version = info.version,
|
||||
sizeMB = (info.size / 1024 / 1024).toInt(),
|
||||
source = info.source
|
||||
source = info.source,
|
||||
variant = info.variant
|
||||
)
|
||||
} else {
|
||||
ApkPreparationStatus.Error("Cached APK info not found")
|
||||
@ -316,7 +351,8 @@ class ApkDownloadViewModel(application: Application) : AndroidViewModel(applicat
|
||||
ApkPreparationStatus.Ready(
|
||||
version = info.version,
|
||||
sizeMB = (info.size / 1024 / 1024).toInt(),
|
||||
source = info.source
|
||||
source = info.source,
|
||||
variant = info.variant
|
||||
)
|
||||
} else {
|
||||
val partial = apkManager.getPartialDownloadProgress()
|
||||
|
||||
@ -31,18 +31,21 @@ object DistributionInfoProvider {
|
||||
val splitApks = applicationInfo.splitSourceDirs.orEmpty()
|
||||
val installerPackage = installerPackageName(context)
|
||||
val certificateSha256 = signingCertificateSha256(packageInfo)
|
||||
val installedApkCanBeSharedUniversally = splitApks.isEmpty() &&
|
||||
isUniversalApk(File(applicationInfo.sourceDir))
|
||||
val installedApkVariant = if (splitApks.isEmpty()) {
|
||||
shareableApkVariant(File(applicationInfo.sourceDir))
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
return DistributionInfo(
|
||||
installSource = installSourceLabel(installerPackage),
|
||||
installerPackage = installerPackage,
|
||||
packageFormat = if (splitApks.isEmpty()) "Standalone APK" else "Split APK set",
|
||||
architecture = architectureLabel(applicationInfo.sourceDir, splitApks),
|
||||
sharingSource = if (installedApkCanBeSharedUniversally) {
|
||||
"Current installed APK"
|
||||
} else {
|
||||
"Verified GitHub universal APK"
|
||||
sharingSource = when (installedApkVariant) {
|
||||
ShareableApkVariant.UNIVERSAL -> "Current installed APK"
|
||||
ShareableApkVariant.ARM64 -> "Current installed APK (ARM64)"
|
||||
null -> "Verified GitHub universal APK"
|
||||
},
|
||||
versionName = packageInfo.versionName ?: BuildConfig.VERSION_NAME,
|
||||
versionCode = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
@ -112,6 +115,21 @@ object DistributionInfoProvider {
|
||||
return packagedAbis.isEmpty() || packagedAbis.containsAll(UNIVERSAL_RELEASE_ABIS)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the compatibility of an APK that is safe to offer for sharing.
|
||||
* ARM64 is intentionally the only architecture-limited release variant
|
||||
* supported because it is the project's primary per-ABI build.
|
||||
*/
|
||||
fun shareableApkVariant(apk: File): ShareableApkVariant? {
|
||||
val packagedAbis = nativeAbisInApk(apk)
|
||||
return when {
|
||||
packagedAbis.isEmpty() || packagedAbis.containsAll(UNIVERSAL_RELEASE_ABIS) ->
|
||||
ShareableApkVariant.UNIVERSAL
|
||||
packagedAbis == setOf("arm64-v8a") -> ShareableApkVariant.ARM64
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
internal fun nativeAbisInApk(apk: File): Set<String> {
|
||||
if (!apk.isFile) return emptySet()
|
||||
return try {
|
||||
@ -189,3 +207,8 @@ object DistributionInfoProvider {
|
||||
val certificateSha256: String?
|
||||
)
|
||||
}
|
||||
|
||||
enum class ShareableApkVariant {
|
||||
UNIVERSAL,
|
||||
ARM64
|
||||
}
|
||||
|
||||
@ -24,7 +24,7 @@ import java.nio.file.StandardCopyOption
|
||||
import java.security.MessageDigest
|
||||
|
||||
/**
|
||||
* Manages downloading, caching, and verifying the universal APK for offline sharing.
|
||||
* Manages local and downloaded APK artifacts for offline sharing.
|
||||
*/
|
||||
class UniversalApkManager(private val context: Context) {
|
||||
|
||||
@ -54,7 +54,7 @@ class UniversalApkManager(private val context: Context) {
|
||||
.build()
|
||||
|
||||
/**
|
||||
* Get information about the cached universal APK, if it exists.
|
||||
* Get information about the cached sharing APK, if it exists.
|
||||
*/
|
||||
fun getCachedApkInfo(): ApkInfo? {
|
||||
return try {
|
||||
@ -81,6 +81,13 @@ class UniversalApkManager(private val context: Context) {
|
||||
Log.w(TAG, "Metadata exists but APK file not found: ${apkFile.path}")
|
||||
return null
|
||||
}
|
||||
val variant = runCatching {
|
||||
ShareableApkVariant.valueOf(json.optString("variant"))
|
||||
}.getOrNull() ?: DistributionInfoProvider.shareableApkVariant(apkFile)
|
||||
if (variant == null) {
|
||||
Log.w(TAG, "Cached APK is not a supported sharing variant")
|
||||
return null
|
||||
}
|
||||
|
||||
ApkInfo(
|
||||
version = version,
|
||||
@ -88,7 +95,8 @@ class UniversalApkManager(private val context: Context) {
|
||||
downloadDate = downloadDate,
|
||||
size = size,
|
||||
file = apkFile,
|
||||
source = source
|
||||
source = source,
|
||||
variant = variant
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error reading cached APK info", e)
|
||||
@ -125,11 +133,10 @@ class UniversalApkManager(private val context: Context) {
|
||||
*/
|
||||
suspend fun checkForUpdate(): UpdateStatus = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
// A genuinely universal standalone APK is already an installable
|
||||
// sharing artifact. Architecture-specific standalone APKs and split
|
||||
// installs still need the universal GitHub artifact.
|
||||
// A supported standalone APK is already an installable sharing
|
||||
// artifact. Split installs still need the universal GitHub artifact.
|
||||
val installedApkInfo = cacheInstalledApkIfPreferred()
|
||||
if (installedApkInfo != null) {
|
||||
if (installedApkInfo?.source == ApkSource.INSTALLED) {
|
||||
return@withContext UpdateStatus.UpToDate(installedApkInfo.version)
|
||||
}
|
||||
|
||||
@ -330,7 +337,8 @@ class UniversalApkManager(private val context: Context) {
|
||||
checksum = release.universalApkSha256 ?: "",
|
||||
size = finalFile.length(),
|
||||
fileName = finalFileName,
|
||||
source = ApkSource.GITHUB
|
||||
source = ApkSource.GITHUB,
|
||||
variant = ShareableApkVariant.UNIVERSAL
|
||||
)
|
||||
cleanupOldApks(except = finalFile)
|
||||
|
||||
@ -467,9 +475,8 @@ class UniversalApkManager(private val context: Context) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Cache the APK this process was installed from only when it is both
|
||||
* standalone and universal. A base APK from a split install is incomplete,
|
||||
* while an ABI-specific APK would unnecessarily limit recipients.
|
||||
* Cache the APK this process was installed from when it is a standalone
|
||||
* universal or ARM64 artifact. A base APK from a split install is incomplete.
|
||||
*/
|
||||
private fun cacheInstalledApkIfPreferred(): ApkInfo? {
|
||||
return try {
|
||||
@ -482,15 +489,25 @@ class UniversalApkManager(private val context: Context) {
|
||||
if (!installedApk.isFile || installedApk.length() <= 0L) {
|
||||
return null
|
||||
}
|
||||
if (!DistributionInfoProvider.isUniversalApk(installedApk)) {
|
||||
Log.d(TAG, "Installed APK is architecture-specific; using GitHub universal APK")
|
||||
discardArchitectureLimitedInstalledCache()
|
||||
val installedVariant = DistributionInfoProvider.shareableApkVariant(installedApk)
|
||||
if (installedVariant == null) {
|
||||
Log.d(TAG, "Installed APK is not a supported sharing variant")
|
||||
return null
|
||||
}
|
||||
|
||||
val installedVersion = installedVersionName()
|
||||
val cachedInfo = getCachedApkInfo()
|
||||
|
||||
// Downloading the universal release is an explicit compatibility
|
||||
// choice. Keep it even when the running ARM64 build is newer; the
|
||||
// user can delete it from the UI to return to the local artifact.
|
||||
if (installedVariant == ShareableApkVariant.ARM64 &&
|
||||
cachedInfo?.source == ApkSource.GITHUB &&
|
||||
cachedInfo.variant == ShareableApkVariant.UNIVERSAL
|
||||
) {
|
||||
return cachedInfo
|
||||
}
|
||||
|
||||
// Keep an already cached artifact if it is the same version or
|
||||
// newer. Otherwise prefer the running build so sharing cannot
|
||||
// silently downgrade recipients to an older GitHub release.
|
||||
@ -502,7 +519,11 @@ class UniversalApkManager(private val context: Context) {
|
||||
|
||||
checkDiskSpace(installedApk.length())
|
||||
val safeVersion = installedVersion.replace(Regex("[^A-Za-z0-9._-]"), "_")
|
||||
val finalFileName = "$APK_FILE_PREFIX$safeVersion.apk"
|
||||
val variantSuffix = when (installedVariant) {
|
||||
ShareableApkVariant.UNIVERSAL -> ""
|
||||
ShareableApkVariant.ARM64 -> "-arm64-v8a"
|
||||
}
|
||||
val finalFileName = "$APK_FILE_PREFIX$safeVersion$variantSuffix.apk"
|
||||
val finalFile = File(cacheDir, finalFileName)
|
||||
val pendingFile = File(cacheDir, "$finalFileName.new")
|
||||
|
||||
@ -519,7 +540,8 @@ class UniversalApkManager(private val context: Context) {
|
||||
checksum = checksum,
|
||||
size = finalFile.length(),
|
||||
fileName = finalFileName,
|
||||
source = ApkSource.INSTALLED
|
||||
source = ApkSource.INSTALLED,
|
||||
variant = installedVariant
|
||||
)
|
||||
cleanupOldApks(except = finalFile)
|
||||
|
||||
@ -531,19 +553,6 @@ class UniversalApkManager(private val context: Context) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun discardArchitectureLimitedInstalledCache() {
|
||||
val cachedInfo = getCachedApkInfo() ?: return
|
||||
if (cachedInfo.source != ApkSource.INSTALLED ||
|
||||
DistributionInfoProvider.isUniversalApk(cachedInfo.file)
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
cachedInfo.file.delete()
|
||||
metadataFile.delete()
|
||||
Log.d(TAG, "Removed architecture-specific installed APK from universal sharing cache")
|
||||
}
|
||||
|
||||
private fun installedVersionName(): String {
|
||||
return context.packageManager
|
||||
.getPackageInfo(context.packageName, 0)
|
||||
@ -729,7 +738,8 @@ class UniversalApkManager(private val context: Context) {
|
||||
checksum: String,
|
||||
size: Long,
|
||||
fileName: String,
|
||||
source: ApkSource
|
||||
source: ApkSource,
|
||||
variant: ShareableApkVariant
|
||||
) {
|
||||
val json = JSONObject().apply {
|
||||
put("version", version)
|
||||
@ -738,6 +748,7 @@ class UniversalApkManager(private val context: Context) {
|
||||
put("size", size)
|
||||
put("fileName", fileName)
|
||||
put("source", source.name)
|
||||
put("variant", variant.name)
|
||||
}
|
||||
|
||||
val pendingMetadata = File(cacheDir, "$METADATA_FILE_NAME.new")
|
||||
@ -802,7 +813,8 @@ class UniversalApkManager(private val context: Context) {
|
||||
val downloadDate: Long,
|
||||
val size: Long,
|
||||
val file: File,
|
||||
val source: ApkSource
|
||||
val source: ApkSource,
|
||||
val variant: ShareableApkVariant
|
||||
)
|
||||
|
||||
enum class ApkSource {
|
||||
|
||||
@ -192,7 +192,9 @@
|
||||
<string name="prepare_apk_status_not_downloaded">Not ready • Tap to download</string>
|
||||
<string name="prepare_apk_status_ready">Ready to share</string>
|
||||
<string name="prepare_apk_source_installed" translatable="false">Sharing source: this installed APK</string>
|
||||
<string name="prepare_apk_source_installed_arm64" translatable="false">Sharing source: this installed APK • ARM64 devices only</string>
|
||||
<string name="prepare_apk_source_github" translatable="false">Sharing source: verified GitHub universal APK</string>
|
||||
<string name="prepare_apk_get_universal" translatable="false">Get universal</string>
|
||||
<string name="prepare_apk_status_downloading">Downloading… %1$d%%</string>
|
||||
<string name="prepare_apk_status_update_available">Update available</string>
|
||||
<string name="prepare_apk_button_prepare">Prepare</string>
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
package com.bitchat.android.util
|
||||
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
@ -22,6 +24,10 @@ class DistributionInfoProviderTest {
|
||||
val apk = createApk("lib/arm64-v8a/libbitchat.so")
|
||||
|
||||
assertFalse(DistributionInfoProvider.isUniversalApk(apk))
|
||||
assertEquals(
|
||||
ShareableApkVariant.ARM64,
|
||||
DistributionInfoProvider.shareableApkVariant(apk)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -34,6 +40,10 @@ class DistributionInfoProviderTest {
|
||||
)
|
||||
|
||||
assertTrue(DistributionInfoProvider.isUniversalApk(apk))
|
||||
assertEquals(
|
||||
ShareableApkVariant.UNIVERSAL,
|
||||
DistributionInfoProvider.shareableApkVariant(apk)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -41,6 +51,17 @@ class DistributionInfoProviderTest {
|
||||
val apk = createApk("classes.dex")
|
||||
|
||||
assertTrue(DistributionInfoProvider.isUniversalApk(apk))
|
||||
assertEquals(
|
||||
ShareableApkVariant.UNIVERSAL,
|
||||
DistributionInfoProvider.shareableApkVariant(apk)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `other architecture-only APK is not offered for sharing`() {
|
||||
val apk = createApk("lib/x86_64/libbitchat.so")
|
||||
|
||||
assertNull(DistributionInfoProvider.shareableApkVariant(apk))
|
||||
}
|
||||
|
||||
private fun createApk(vararg entries: String): File {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user