Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import org.mozilla.tryfox.data.SearchHistoryQueryType
import org.mozilla.tryfox.data.repositories.UserDataRepository
import org.mozilla.tryfox.lan.LanReceiveIdentity
import org.mozilla.tryfox.model.HomeScreenLayout
import org.mozilla.tryfox.util.DEFAULT_PREFERRED_ABI

/**
* A fake implementation of [org.mozilla.tryfox.data.repositories.UserDataRepository] for testing purposes.
Expand All @@ -22,6 +23,8 @@ class FakeUserDataRepository : UserDataRepository {
override val lanReceiveIdentityFlow: Flow<LanReceiveIdentity?> = _lanReceiveIdentityFlow
private val _homeScreenLayoutFlow = MutableStateFlow(HomeScreenLayout.OneCardPerApp)
override val homeScreenLayoutFlow: Flow<HomeScreenLayout> = _homeScreenLayoutFlow
private val _preferredAbiFlow = MutableStateFlow(DEFAULT_PREFERRED_ABI)
override val preferredAbiFlow: Flow<String> = _preferredAbiFlow

override suspend fun saveLastSearchedEmail(email: String) {
recordSearch("try", email)
Expand All @@ -44,6 +47,10 @@ class FakeUserDataRepository : UserDataRepository {
_homeScreenLayoutFlow.value = layout
}

override suspend fun savePreferredAbi(abiName: String) {
_preferredAbiFlow.value = abiName
}

// Helper method for tests to clear the stored email if needed
fun clearLastSearchedEmail() {
_lastSearchedEmailFlow.value = ""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import org.mozilla.tryfox.util.REFERENCE_BROWSER
import org.mozilla.tryfox.util.REFERENCE_BROWSER_PACKAGE
import org.mozilla.tryfox.util.TRYFOX
import org.mozilla.tryfox.util.TRYFOX_PACKAGE
import org.mozilla.tryfox.util.abiFromNativeLibraryDir

class DefaultMozillaPackageManager(private val context: Context) : MozillaPackageManager {

Expand Down Expand Up @@ -61,6 +62,7 @@ class DefaultMozillaPackageManager(private val context: Context) : MozillaPackag
versionCode = packageInfo?.let { PackageInfoCompat.getLongVersionCode(it) },
installDateMillis = packageInfo?.lastUpdateTime,
installingPackageName = if (packageInfo != null) getInstallingPackageName(packageName) else null,
activeAbi = abiFromNativeLibraryDir(packageInfo?.applicationInfo?.nativeLibraryDir),
splitNames = packageInfo?.applicationInfo?.splitNames?.toList() ?: emptyList(),
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import org.mozilla.tryfox.data.SearchHistoryEntry
import org.mozilla.tryfox.data.SearchHistoryQueryType
import org.mozilla.tryfox.lan.LanReceiveIdentity
import org.mozilla.tryfox.model.HomeScreenLayout
import org.mozilla.tryfox.util.DEFAULT_PREFERRED_ABI

/**
* A repository that stores the last searched email in a DataStore.
Expand All @@ -31,6 +32,7 @@ class DefaultUserDataRepository(private val appContext: Context) : UserDataRepos
val LAN_DEVICE_NAME = stringPreferencesKey("lan_device_name")
val LAN_SHARED_SECRET = stringPreferencesKey("lan_shared_secret")
val HOME_SCREEN_LAYOUT = stringPreferencesKey("home_screen_layout")
val PREFERRED_ABI = stringPreferencesKey("preferred_abi")
}

override val searchHistoryFlow: Flow<List<SearchHistoryEntry>> = appContext.dataStore.data.map { preferences ->
Expand Down Expand Up @@ -66,6 +68,10 @@ class DefaultUserDataRepository(private val appContext: Context) : UserDataRepos
homeScreenLayoutFromStoredValue(preferences[PreferenceKeys.HOME_SCREEN_LAYOUT])
}

override val preferredAbiFlow: Flow<String> = appContext.dataStore.data.map { preferences ->
preferences[PreferenceKeys.PREFERRED_ABI]?.takeIf { it.isNotBlank() } ?: DEFAULT_PREFERRED_ABI
}

override suspend fun saveLastSearchedEmail(email: String) {
recordSearch(project = "try", query = email)
}
Expand Down Expand Up @@ -102,6 +108,12 @@ class DefaultUserDataRepository(private val appContext: Context) : UserDataRepos
}
}

override suspend fun savePreferredAbi(abiName: String) {
appContext.dataStore.edit { preferences ->
preferences[PreferenceKeys.PREFERRED_ABI] = abiName
}
}

private fun decodeSearchHistory(serializedHistory: String): List<SearchHistoryEntry> =
runCatching { json.decodeFromString<List<SearchHistoryEntry>>(serializedHistory) }.getOrDefault(emptyList())

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ interface UserDataRepository {
val lanReceiveIdentityFlow: Flow<LanReceiveIdentity?>
val homeScreenLayoutFlow: Flow<HomeScreenLayout>

/** The ABI variant to favour when a build offers several. Emits `universal` by default. */
val preferredAbiFlow: Flow<String>

/**
* Saves the last searched email.
* @param email The email to save.
Expand All @@ -26,4 +29,5 @@ interface UserDataRepository {
suspend fun recordSearch(project: String, query: String, searchedAt: Long = System.currentTimeMillis())
suspend fun saveLanReceiveIdentity(identity: LanReceiveIdentity)
suspend fun saveHomeScreenLayout(layout: HomeScreenLayout)
suspend fun savePreferredAbi(abiName: String)
}
1 change: 1 addition & 0 deletions app/src/main/java/org/mozilla/tryfox/model/AppState.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ data class AppState(
val installDateMillis: Long?,
val installingPackageName: String? = null,
val versionCode: Long? = null,
val activeAbi: String? = null,
val splitNames: List<String> = emptyList(),
) {
val isInstalled: Boolean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ import org.mozilla.tryfox.install.InstallState
import org.mozilla.tryfox.model.AppState
import org.mozilla.tryfox.ui.models.ApkUiModel
import org.mozilla.tryfox.ui.models.NightlyBuildOption
import org.mozilla.tryfox.ui.models.preferredAbiApk
import org.mozilla.tryfox.util.DEFAULT_PREFERRED_ABI
import org.mozilla.tryfox.util.FENIX
import org.mozilla.tryfox.util.FENIX_BETA
import org.mozilla.tryfox.util.FENIX_RELEASE
Expand Down Expand Up @@ -124,6 +126,7 @@ fun ArchiveGroupCard(
onDismissBuildPicker: () -> Unit = {},
installStates: Map<String, InstallState> = emptyMap(),
onOpenInstalledApp: (String) -> Unit = {},
preferredAbi: String = DEFAULT_PREFERRED_ABI,
) {
if (pendingBuildOptions.isNotEmpty()) {
NightlyBuildPickerDialog(
Expand Down Expand Up @@ -200,6 +203,7 @@ fun ArchiveGroupCard(
appState,
installStates,
onOpenInstalledApp,
preferredAbi,
)
}

Expand Down Expand Up @@ -623,9 +627,13 @@ private fun ArchiveGroupAbiSelector(
appState: AppState?,
installStates: Map<String, InstallState>,
onOpenInstalledApp: (String) -> Unit,
preferredAbi: String,
) {
val firstSupportedIndex = apks.indexOfFirst { it.abi.isSupported }.takeIf { it != -1 } ?: 0
var selectedIndex by remember { mutableStateOf(firstSupportedIndex) }
val preferredIndex = apks.preferredAbiApk(preferredAbi)
?.let(apks::indexOf)
?.takeIf { it != -1 }
?: 0
var selectedIndex by remember { mutableStateOf(preferredIndex) }

Column(
horizontalAlignment = Alignment.CenterHorizontally,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,13 @@ private fun InstallMetadataBottomSheet(
label = stringResource(id = R.string.install_metadata_source_label),
value = installSourceLabel(appState),
)
InstallMetadataRow(
// The ABI Android selected at install time. A universal APK reports the single
// architecture the platform kept, so this does not identify the APK variant.
label = stringResource(id = R.string.install_metadata_abi_label),
value = appState.activeAbi
?: stringResource(id = R.string.install_metadata_abi_none),
)
InstallMetadataBlock(
label = stringResource(id = R.string.install_metadata_splits_label),
// splitNames covers only the config splits; the base APK is always present but not
Expand Down Expand Up @@ -233,7 +240,7 @@ private fun InstallMetadataBlock(label: String, value: String) {
text = value,
style = MaterialTheme.typography.bodySmall,
fontFamily = FontFamily.Monospace,
modifier = Modifier.padding(top = 2.dp),
modifier = Modifier.padding(start = 8.dp, top = 2.dp),
)
}
}
Expand Down
17 changes: 16 additions & 1 deletion app/src/main/java/org/mozilla/tryfox/ui/models/AbiUiModel.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
package org.mozilla.tryfox.ui.models

import org.mozilla.tryfox.util.isUniversalAbi

data class AbiUiModel(
val name: String?,
val isSupported: Boolean,
)
) {
val isUniversal: Boolean get() = isUniversalAbi(name)
}

/**
* Picks the variant matching [preferredAbi], falling back to universal, then to anything the
* device can install, then to whatever came first. Nightly builds ship no universal variant, so
* an unmatched affinity still resolves to an installable APK.
*/
fun List<ApkUiModel>.preferredAbiApk(preferredAbi: String): ApkUiModel? =
firstOrNull { preferredAbi.equals(it.abi.name, ignoreCase = true) }
?: firstOrNull { it.abi.isUniversal }
?: firstOrNull { it.abi.isSupported }
?: firstOrNull()
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ data class AppUiModel(
val installedDate: String?,
val installingPackageName: String? = null,
val splitNames: List<String> = emptyList(),
val activeAbi: String? = null,
val installedTryBuild: InstalledTryBuild? = null,
val apks: ApksResult,
val userPickedDate: LocalDate? = null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ import org.mozilla.tryfox.ui.models.ApkUiModel
import org.mozilla.tryfox.ui.models.ApksResult
import org.mozilla.tryfox.ui.models.AppUiModel
import org.mozilla.tryfox.ui.models.NightlyBuildOption
import org.mozilla.tryfox.ui.models.preferredAbiApk
import org.mozilla.tryfox.util.DEFAULT_PREFERRED_ABI
import org.mozilla.tryfox.util.FENIX
import org.mozilla.tryfox.util.FENIX_BETA
import org.mozilla.tryfox.util.FENIX_DEBUG
Expand Down Expand Up @@ -94,6 +96,7 @@ internal fun HomeAppCard(
onBuildSelected: (String, String) -> Unit,
onDismissBuildPicker: (String) -> Unit,
modifier: Modifier = Modifier,
preferredAbi: String = DEFAULT_PREFERRED_ABI,
) {
val app = card.selectedApp
val appState = app.toAppState()
Expand All @@ -104,8 +107,7 @@ internal fun HomeAppCard(
val isNightly = app.name == FENIX || app.name == FOCUS
val isDebug = app.name == FENIX_DEBUG || app.name == FOCUS_DEBUG
val isVersionSelectable = app.name in setOf(FENIX_RELEASE, FENIX_BETA, FOCUS_RELEASE, FOCUS_BETA)
val selectedApk = (app.apks as? ApksResult.Success)?.apks
?.let { apks -> apks.firstOrNull { it.abi.isSupported } ?: apks.firstOrNull() }
val selectedApk = (app.apks as? ApksResult.Success)?.apks?.preferredAbiApk(preferredAbi)

Card(
modifier = modifier
Expand Down Expand Up @@ -340,6 +342,7 @@ private fun AppUiModel.toAppState(): AppState? = installedVersion?.let { version
installDateMillis = installedDate?.let(::parseDateToMillis),
installingPackageName = installingPackageName,
versionCode = installedVersionCode,
activeAbi = activeAbi,
splitNames = splitNames,
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ fun HomeScreen(
onDismissBuildPicker = { appName ->
homeViewModel.onDismissBuildPicker(appName)
},
preferredAbi = currentScreenState.preferredAbi,
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package org.mozilla.tryfox.ui.screens
import org.mozilla.tryfox.model.CacheManagementState
import org.mozilla.tryfox.model.HomeScreenLayout
import org.mozilla.tryfox.ui.models.AppUiModel
import org.mozilla.tryfox.util.DEFAULT_PREFERRED_ABI

/**
* Represents the various states of the Home screen.
Expand All @@ -23,5 +24,6 @@ sealed class HomeScreenState {
val isDownloadingAnyFile: Boolean,
val selectedAppNames: Map<HomeAppFamily, String> = HomeAppFamily.entries.associateWith { it.defaultAppName },
val homeScreenLayout: HomeScreenLayout = HomeScreenLayout.OneCardPerApp,
val preferredAbi: String = DEFAULT_PREFERRED_ABI,
) : HomeScreenState()
}
33 changes: 27 additions & 6 deletions app/src/main/java/org/mozilla/tryfox/ui/screens/HomeViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import org.mozilla.tryfox.ui.models.ApksResult
import org.mozilla.tryfox.ui.models.AppUiModel
import org.mozilla.tryfox.ui.models.NightlyBuildOption
import org.mozilla.tryfox.ui.models.newVersionAvailable
import org.mozilla.tryfox.util.DEFAULT_PREFERRED_ABI
import org.mozilla.tryfox.util.FENIX
import org.mozilla.tryfox.util.FENIX_BETA
import org.mozilla.tryfox.util.FENIX_DEBUG
Expand All @@ -60,6 +61,8 @@ import org.mozilla.tryfox.util.FOCUS_DEBUG
import org.mozilla.tryfox.util.FOCUS_RELEASE
import org.mozilla.tryfox.util.REFERENCE_BROWSER
import org.mozilla.tryfox.util.TRYFOX
import org.mozilla.tryfox.util.isAbiSupported
import org.mozilla.tryfox.util.resolvePreferredAbi
import java.io.File

/**
Expand Down Expand Up @@ -100,6 +103,9 @@ class HomeViewModel(
private var tryFoxCardDismissed = false
private var homeScreenLayout = HomeScreenLayout.OneCardPerApp

@Volatile
private var preferredAbi = DEFAULT_PREFERRED_ABI

@Volatile
private var selectedHomeAppNames = HomeAppFamily.entries.associateWith { it.defaultAppName }
private val appMutationVersions = mutableMapOf<String, Long>()
Expand Down Expand Up @@ -139,6 +145,20 @@ class HomeViewModel(
}
?.launchIn(viewModelScope)

userDataRepository?.preferredAbiFlow
?.onEach { storedAbi ->
val resolvedAbi = resolvePreferredAbi(storedAbi, supportedAbis)
preferredAbi = resolvedAbi
_homeScreenState.update { currentState ->
if (currentState is HomeScreenState.Loaded) {
currentState.copy(preferredAbi = resolvedAbi)
} else {
currentState
}
}
}
?.launchIn(viewModelScope)

mozillaPackageManager.appStates
.onEach { appState ->
_homeScreenState.update { currentState ->
Expand All @@ -151,6 +171,7 @@ class HomeViewModel(
installedVersionCode = appState.versionCode,
installedDate = appState.formattedInstallDate,
installingPackageName = appState.installingPackageName,
activeAbi = appState.activeAbi,
splitNames = appState.splitNames,
installedTryBuild = app.name.takeIf { it == FENIX_DEBUG }
?.let { matchingInstalledTryBuild(appState) },
Expand Down Expand Up @@ -327,6 +348,7 @@ class HomeViewModel(
installedDate = appState?.formattedInstallDate,
installingPackageName = appState?.installingPackageName,
splitNames = appState?.splitNames ?: emptyList(),
activeAbi = appState?.activeAbi,
installedTryBuild = appState?.takeIf { name == FENIX_DEBUG }
?.let { matchingInstalledTryBuild(it) },
)
Expand All @@ -343,6 +365,7 @@ class HomeViewModel(
isDownloadingAnyFile = false,
selectedAppNames = selectedHomeAppNames,
homeScreenLayout = homeScreenLayout,
preferredAbi = preferredAbi,
).applyDownloadStates(downloadStates.value)
}

Expand Down Expand Up @@ -401,6 +424,7 @@ class HomeViewModel(
installedDate = appState?.formattedInstallDate,
installingPackageName = appState?.installingPackageName,
splitNames = appState?.splitNames.orEmpty(),
activeAbi = appState?.activeAbi,
installedTryBuild = appState?.takeIf { appName == FENIX_DEBUG }?.let(::matchingInstalledTryBuild),
apks = ApksResult.Success(apks.map { it.toUiModel() }),
selectedReleaseVersion = selectedReleaseVersion,
Expand Down Expand Up @@ -476,6 +500,7 @@ class HomeViewModel(
installedDate = appState?.formattedInstallDate,
installingPackageName = appState?.installingPackageName,
splitNames = appState?.splitNames ?: emptyList(),
activeAbi = appState?.activeAbi,
installedTryBuild = appState?.takeIf { repository.appName == FENIX_DEBUG }?.let(::matchingInstalledTryBuild),
apks = apksResult,
selectedReleaseVersion = selectedReleaseVersion,
Expand All @@ -487,12 +512,8 @@ class HomeViewModel(
return parsedApks.map { parsedApk ->
val buildDate = parsedApk.rawDateString?.rawNightlyBuildDate()
val date = buildDate?.let { parsedApk.rawDateString?.formatNightlyBuildDate().orEmpty() }.orEmpty()
val isCompatible = supportedAbis.any { deviceAbi ->
deviceAbi.equals(
parsedApk.abiName,
ignoreCase = true,
)
}
// Universal APKs bundle every architecture, so they install anywhere.
val isCompatible = isAbiSupported(parsedApk.abiName, supportedAbis)

// Key the cache dir / unique key by the full build timestamp (yyyy-MM-dd-HH-mm-ss) so
// two Nightly builds from the same day don't collide on a date-only path. Releases have
Expand Down
Loading
Loading