Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
5ab417b
Use WorkManager to download apks in background.
Jul 11, 2026
999d994
Improve server screen layout and navigation
Jul 23, 2026
1a54144
Unify build search entry points
Jul 30, 2026
d632570
Refine unified search layout
Jul 30, 2026
aa313d5
Redesign email search results
Jul 30, 2026
581a359
Polish search result job presentation
Jul 30, 2026
f506593
Refine unified search controls
Jul 30, 2026
03b8b2c
Polish search navigation and selector
Jul 30, 2026
b71fc8d
Add animated progress button
Jul 31, 2026
beca879
Add unified search history
Jul 31, 2026
c0de3a7
Polish search result labels and icons
Jul 31, 2026
4a7009e
Unify search result and input presentation
Jul 31, 2026
4a18503
Animate search result editing
Jul 31, 2026
2821863
Unify search result flow
Jul 31, 2026
b748695
Manage unified search APK installs
Jul 31, 2026
21c60ec
Use managed installer across app
Jul 31, 2026
a4f5d21
Cache home screen release data
Jul 31, 2026
9778a79
Show unsigned APKs in search results
Jul 31, 2026
ced9f82
Warn before unsigned APK downloads
Jul 31, 2026
a99db78
Hide unsigned APKs with signed equivalents
Jul 31, 2026
2e4937a
Keep search controls fixed above suggestions
Jul 31, 2026
ed4ff42
Add recent project build search fallback
Aug 3, 2026
632a82c
Order APK jobs by variant and name
Aug 3, 2026
bd93be2
Keep search field empty on open
Aug 3, 2026
909ea32
Redesign home app cards
Aug 4, 2026
9fb05f4
Show unsigned Roam builds and unknown app icons
Aug 4, 2026
425404d
Show TryFox installer icon on installed status
Aug 4, 2026
6ecd93a
Add installed Firefox Debug home flavor
Aug 4, 2026
ff0f2cd
Improve installed app source and flavor icons
Aug 4, 2026
52374bf
Show real commit titles for Try pushes
Aug 4, 2026
6d66c83
Show installed Fenix Debug Try revision on Home
Aug 5, 2026
9f09a78
Preserve Home flavor selection on refresh
Aug 5, 2026
946516f
Format displayed commit messages
Aug 5, 2026
3cf7653
Add paginated search results
Aug 5, 2026
f805ec1
Highlight installed home flavors
Aug 5, 2026
2af6ffc
Remove duplicate search loading indicator
Aug 5, 2026
4a68385
Improve search pagination and nightly dates
Aug 6, 2026
3c84ab1
Refine search pagination fallback
Aug 6, 2026
c6badc2
Remove fetched push summary warning
Aug 6, 2026
b1bd328
Fix lint violations
Aug 6, 2026
f0bcbf4
Route all APK installs through PackageInstaller
Aug 6, 2026
8a00578
Add settings screen and cache controls
Aug 6, 2026
492ed8d
Add directional screen transitions
Aug 6, 2026
f68ca41
Support per-flavor home cards
Aug 6, 2026
cfadf9b
Move cache clearing to settings
Aug 6, 2026
d672741
Avoid loading debug build metadata
Aug 6, 2026
f807a46
Fix lint and ktlint formatting issues
Aug 7, 2026
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
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,13 @@ Install the latest APK from [TryFox Releases](https://github.com/mozilla-mobile/
- `mozilla-central` (displayed as "central")
- `mozilla-beta` (displayed as "beta")
- `mozilla-release` (displayed as "release")
- `autoland` (displayed as "autoland")
- **Search by Revision**: Enter a full revision hash to find associated builds for the selected project.
- **Automated CI Data Fetching**:
1. Queries Treeherder API for push details using the selected project and revision.
2. Displays relevant push comment from the revision (often a Bugzilla link).
3. Fetches all jobs associated with the push.
4. Filters for relevant, signed, non-test build jobs (jobs containing "B" and "s", excluding "t" in their symbols).
4. Filters for relevant Android APK build jobs, including signed and unsigned builds, while excluding test jobs.
5. For each job, retrieves its artifacts from Taskcluster.
- **Job and Artifact Display**:
- Lists build jobs with their app icon (e.g., Fenix, Focus), job name, job symbol, and Task ID.
Expand Down Expand Up @@ -106,7 +107,7 @@ Run Android instrumentation tests with a connected device or running emulator:
- **Retrofit** for type-safe HTTP requests to Treeherder and Taskcluster APIs.
- **Kotlinx Serialization** for efficient JSON parsing.
- **OkHttp** as the underlying HTTP client for Retrofit (with a `HttpLoggingInterceptor` for debugging network traffic).
- **FileProvider** for securely sharing downloaded APKs with the system package installer.
- **PackageInstaller sessions** for installing downloaded APKs directly from TryFox.

## Requirements

Expand Down
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ dependencies {
// Additional Compose dependencies
implementation(libs.androidx.compose.material.icons.extended)
implementation(libs.androidx.lifecycle.viewmodel.compose)
implementation(libs.androidx.work.runtime.ktx)

// DataStore
implementation(libs.androidx.datastore.preferences)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ class MainActivityDeeplinkTest {
}

@Test
fun testDeeplink_withAuthorEmail_populatesProfileScreen() {
fun testDeeplink_withAuthorEmail_populatesSearchScreen() {
val email = "tthibaud@mozilla.com"
val encodedEmail = "tthibaud%40mozilla.com"
val deeplinkUri =
Expand Down Expand Up @@ -118,7 +118,7 @@ class MainActivityDeeplinkTest {
}

@Test
fun testTryfoxScheme_withAuthorEmail_populatesProfileScreen() {
fun testTryfoxScheme_withAuthorEmail_populatesSearchScreen() {
val email = "tthibaud@mozilla.com"
val encodedEmail = "tthibaud%40mozilla.com"
val deeplinkUri =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.mozilla.tryfox.data

import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
import org.mozilla.tryfox.download.ApkDownloadCoordinator
import org.mozilla.tryfox.download.ApkDownloadRequest
import org.mozilla.tryfox.download.model.PersistedDownloadState

class FakeApkDownloadCoordinator : ApkDownloadCoordinator {
override val downloads = MutableStateFlow<Map<String, PersistedDownloadState>>(emptyMap())

override fun enqueue(request: ApkDownloadRequest): String = request.uniqueKey
override fun retry(request: ApkDownloadRequest): String = request.uniqueKey
override fun cancel(uniqueKey: String) = Unit
override fun observe(uniqueKey: String): Flow<PersistedDownloadState?> = flowOf(downloads.value[uniqueKey])
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@ import java.io.File
class FakeCacheManager : CacheManager {
private val _cacheState = MutableStateFlow<CacheManagementState>(CacheManagementState.IdleEmpty)
override val cacheState: StateFlow<CacheManagementState> = _cacheState
private val _cacheSizeBytes = MutableStateFlow(0L)
override val cacheSizeBytes: StateFlow<Long> = _cacheSizeBytes

override suspend fun clearCache() {
_cacheState.value = CacheManagementState.IdleEmpty
_cacheSizeBytes.value = 0L
}

override fun checkCacheStatus() {
override suspend fun checkCacheStatus() {
// No-op for now
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class FakeDownloadFileRepository(
override suspend fun downloadFile(
downloadUrl: String,
outputFile: File,
onProgress: (bytesDownloaded: Long, totalBytes: Long) -> Unit,
onProgress: suspend (bytesDownloaded: Long, totalBytes: Long) -> Unit,
): NetworkResult<File> {
downloadFileCalled = true

Expand Down
Original file line number Diff line number Diff line change
@@ -1,38 +1,14 @@
package org.mozilla.tryfox.data

import org.mozilla.tryfox.data.managers.IntentManager
import java.io.File

/**
* A fake implementation of [IntentManager] for use in instrumented tests.
* This class allows for verifying that the `installApk` method is called with the correct file.
*/
class FakeIntentManager() : IntentManager {

var wasUninstallApkCalled: Boolean = false
private set

/**
* A boolean flag to indicate whether the `installApk` method was called.
*/
val wasInstallApkCalled: Boolean
get() = installedFile != null

/**
* The file that was passed to the `installApk` method.
*/
var installedFile: File? = null
private set

/**
* Overrides the `installApk` method to capture the file and set the `wasInstallApkCalled` flag.
*
* @param file The file to be "installed".
*/
override fun installApk(file: File) {
installedFile = file
}

override fun uninstallApk(packageName: String) {
wasUninstallApkCalled = true
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@ package org.mozilla.tryfox.data

import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import org.mozilla.tryfox.data.SearchHistory
import org.mozilla.tryfox.data.SearchHistoryEntry
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

/**
* A fake implementation of [org.mozilla.tryfox.data.repositories.UserDataRepository] for testing purposes.
Expand All @@ -12,19 +16,37 @@ class FakeUserDataRepository : UserDataRepository {

private val _lastSearchedEmailFlow = MutableStateFlow("")
override val lastSearchedEmailFlow: Flow<String> = _lastSearchedEmailFlow
private val _searchHistoryFlow = MutableStateFlow<List<SearchHistoryEntry>>(emptyList())
override val searchHistoryFlow: Flow<List<SearchHistoryEntry>> = _searchHistoryFlow
private val _lanReceiveIdentityFlow = MutableStateFlow<LanReceiveIdentity?>(null)
override val lanReceiveIdentityFlow: Flow<LanReceiveIdentity?> = _lanReceiveIdentityFlow
private val _homeScreenLayoutFlow = MutableStateFlow(HomeScreenLayout.OneCardPerApp)
override val homeScreenLayoutFlow: Flow<HomeScreenLayout> = _homeScreenLayoutFlow

override suspend fun saveLastSearchedEmail(email: String) {
_lastSearchedEmailFlow.value = email
recordSearch("try", email)
}

override suspend fun recordSearch(project: String, query: String, searchedAt: Long) {
val queryType = if ('@' in query) SearchHistoryQueryType.EMAIL else SearchHistoryQueryType.REVISION
_searchHistoryFlow.value = SearchHistory.record(
_searchHistoryFlow.value,
SearchHistoryEntry(project, query, queryType, searchedAt),
)
_lastSearchedEmailFlow.value = SearchHistory.latestEmail(_searchHistoryFlow.value)
}

override suspend fun saveLanReceiveIdentity(identity: LanReceiveIdentity) {
_lanReceiveIdentityFlow.value = identity
}

override suspend fun saveHomeScreenLayout(layout: HomeScreenLayout) {
_homeScreenLayoutFlow.value = layout
}

// Helper method for tests to clear the stored email if needed
fun clearLastSearchedEmail() {
_lastSearchedEmailFlow.value = ""
_searchHistoryFlow.value = emptyList()
}
}
Loading
Loading