Skip to content

Commit 4d91a9e

Browse files
Add support for OTA updates and update privacy policy
- Introduced product flavors for GitHub and Play Store builds to manage OTA updates. - Updated Jellyfin references to Moonfin in strings and descriptions. - Added a privacy policy detailing data collection practices and user privacy. - Implemented conditional update checks and UI elements based on the build flavor.
1 parent 9b61f3f commit 4d91a9e

File tree

11 files changed

+177
-58
lines changed

11 files changed

+177
-58
lines changed

.vscode/settings.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"chat.tools.terminal.autoApprove": {
3+
"identify": true
4+
}
5+
}

PRIVACY_POLICY.md

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# Privacy Policy
2+
3+
**Moonfin for Android TV**
4+
Last updated: February 19, 2026
5+
6+
## Overview
7+
8+
Moonfin is a third-party client application for Jellyfin media servers. Moonfin does not operate any servers, collect user data, or track usage. All media data stays between your device and your self-hosted Jellyfin server.
9+
10+
## Data Collection
11+
12+
### Data We Do NOT Collect
13+
14+
- We do not collect personal information
15+
- We do not track your usage or behavior
16+
- We do not serve advertisements
17+
- We do not share any data with third parties
18+
- We do not use analytics services (e.g., Google Analytics, Firebase)
19+
20+
### Data Stored Locally on Your Device
21+
22+
Moonfin stores the following data locally on your device only:
23+
24+
- **Server addresses** — The URLs of Jellyfin servers you connect to
25+
- **Authentication tokens** — Used to maintain your session with your Jellyfin server
26+
- **User preferences** — Display, playback, and customization settings
27+
- **Cached images** — Temporarily cached artwork for performance
28+
29+
This data never leaves your device except when communicating directly with your Jellyfin server.
30+
31+
### Crash Reports (Optional)
32+
33+
Moonfin can send crash reports to **your own Jellyfin server** when the app encounters an error. This feature:
34+
35+
- Is **opt-in** and can be disabled in Settings > Telemetry
36+
- Sends crash data **only to your own server**, never to us or any third party
37+
- May include: stack traces, app version, device model, Android version, and optionally system logs
38+
- Does not include personal information, media content, or browsing history
39+
40+
### Network Communication
41+
42+
Moonfin communicates only with:
43+
44+
- **Your Jellyfin server(s)** — To stream media, authenticate, and sync settings
45+
- **Your Jellyseerr server** (if configured) — To browse and request media
46+
- **GitHub API** (libre/sideloaded builds only) — To check for app updates
47+
48+
Moonfin supports cleartext (HTTP) connections because many self-hosted Jellyfin servers run on local networks without HTTPS. No data is sent to any Moonfin-operated servers.
49+
50+
### Voice Search
51+
52+
Moonfin requests microphone access for voice search functionality on Android TV. Audio is processed by the Android system's speech recognizer and is not recorded, stored, or transmitted by Moonfin.
53+
54+
## Data Security
55+
56+
All sensitive data (authentication tokens, server credentials) is stored in the app's private storage on your device, protected by Android's application sandboxing. Moonfin supports HTTPS connections to your servers for encrypted communication.
57+
58+
## Children's Privacy
59+
60+
Moonfin is not directed at children under the age of 13. We do not knowingly collect any information from children.
61+
62+
## Third-Party Services
63+
64+
Moonfin does not integrate with any third-party analytics, advertising, or tracking services. The only external services Moonfin communicates with are the servers you explicitly configure (Jellyfin, Jellyseerr).
65+
66+
## Changes to This Policy
67+
68+
We may update this privacy policy from time to time. Changes will be posted to this page with an updated revision date.
69+
70+
## Contact
71+
72+
If you have questions about this privacy policy, you can open an issue on our GitHub repository:
73+
https://github.com/Moonfin-Client/AndroidTV-FireTV/issues
74+
75+
## Open Source
76+
77+
Moonfin is open-source software. You can review the complete source code to verify our privacy practices:
78+
https://github.com/Moonfin-Client/AndroidTV-FireTV

app/build.gradle.kts

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,20 @@ android {
3333
isCoreLibraryDesugaringEnabled = true
3434
}
3535

36+
flavorDimensions += "distribution"
37+
38+
productFlavors {
39+
create("github") {
40+
dimension = "distribution"
41+
buildConfigField("boolean", "ENABLE_OTA_UPDATES", "true")
42+
}
43+
44+
create("playstore") {
45+
dimension = "distribution"
46+
buildConfigField("boolean", "ENABLE_OTA_UPDATES", "false")
47+
}
48+
}
49+
3650
signingConfigs {
3751
create("release") {
3852
// Load keystore properties from keystore.properties file
@@ -60,10 +74,10 @@ android {
6074
isShrinkResources = true
6175
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
6276

63-
// Set package names used in various XML files
64-
resValue("string", "app_id", namespace!!)
65-
resValue("string", "app_search_suggest_authority", "${namespace}.content")
66-
resValue("string", "app_search_suggest_intent_data", "content://${namespace}.content/intent")
77+
// Set package names used in various XML files (must match applicationId for provider authorities)
78+
resValue("string", "app_id", defaultConfig.applicationId!!)
79+
resValue("string", "app_search_suggest_authority", "${defaultConfig.applicationId}.content")
80+
resValue("string", "app_search_suggest_intent_data", "content://${defaultConfig.applicationId}.content/intent")
6781

6882
// Set flavored application name
6983
resValue("string", "app_name", "Moonfin")
@@ -78,10 +92,11 @@ android {
7892
isShrinkResources = true
7993
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
8094

81-
// Set package names used in various XML files
82-
resValue("string", "app_id", namespace + applicationIdSuffix)
83-
resValue("string", "app_search_suggest_authority", "${namespace + applicationIdSuffix}.content")
84-
resValue("string", "app_search_suggest_intent_data", "content://${namespace + applicationIdSuffix}.content/intent")
95+
// Set package names used in various XML files (must match applicationId for provider authorities)
96+
val debugAppId = defaultConfig.applicationId + applicationIdSuffix
97+
resValue("string", "app_id", debugAppId)
98+
resValue("string", "app_search_suggest_authority", "${debugAppId}.content")
99+
resValue("string", "app_search_suggest_intent_data", "content://${debugAppId}.content/intent")
85100

86101
// Set flavored application name
87102
resValue("string", "app_name", "Moonfin Debug")

app/src/main/java/org/jellyfin/androidtv/JellyfinApplication.kt

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -99,14 +99,16 @@ class JellyfinApplication : Application(), SingletonImageLoader.Factory {
9999
.build()
100100
).await()
101101

102-
// Schedule update check worker (daily)
103-
workManager.enqueueUniquePeriodicWork(
104-
UpdateCheckWorker.WORK_NAME,
105-
ExistingPeriodicWorkPolicy.KEEP,
106-
PeriodicWorkRequestBuilder<UpdateCheckWorker>(1, TimeUnit.DAYS)
107-
.setBackoffCriteria(BackoffPolicy.LINEAR, 1, TimeUnit.HOURS)
108-
.build()
109-
).await()
102+
// Schedule update check worker (daily) — libre builds only
103+
if (BuildConfig.ENABLE_OTA_UPDATES) {
104+
workManager.enqueueUniquePeriodicWork(
105+
UpdateCheckWorker.WORK_NAME,
106+
ExistingPeriodicWorkPolicy.KEEP,
107+
PeriodicWorkRequestBuilder<UpdateCheckWorker>(1, TimeUnit.DAYS)
108+
.setBackoffCriteria(BackoffPolicy.LINEAR, 1, TimeUnit.HOURS)
109+
.build()
110+
).await()
111+
}
110112
}
111113

112114
launch { socketListener.updateSession() }

app/src/main/java/org/jellyfin/androidtv/data/service/pluginsync/PluginSyncService.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,9 @@ class PluginSyncService(
178178
}
179179

180180
registerChangeListeners()
181-
checkForPluginUpdate(baseUrl, token)
181+
if (org.jellyfin.androidtv.BuildConfig.ENABLE_OTA_UPDATES) {
182+
checkForPluginUpdate(baseUrl, token)
183+
}
182184
}
183185

184186
/**

app/src/main/java/org/jellyfin/androidtv/ui/browsing/MainActivity.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,8 +125,10 @@ class MainActivity : FragmentActivity() {
125125
}
126126
setContentView(binding.root)
127127

128-
// Check for updates on app launch
129-
checkForUpdatesOnLaunch()
128+
// Check for updates on app launch (libre builds only)
129+
if (org.jellyfin.androidtv.BuildConfig.ENABLE_OTA_UPDATES) {
130+
checkForUpdatesOnLaunch()
131+
}
130132
}
131133

132134
private fun setupSyncPlayQueueLauncher() {

app/src/main/java/org/jellyfin/androidtv/ui/settings/screen/SettingsMainScreen.kt

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -156,32 +156,34 @@ fun SettingsMainScreen() {
156156
)
157157
}
158158

159-
item {
160-
ListButton(
161-
leadingContent = {
162-
Icon(
163-
painterResource(R.drawable.ic_get_app),
164-
contentDescription = null
165-
)
166-
},
167-
headingContent = { Text("Check for Updates") },
168-
captionContent = { Text("Download latest Moonfin version") },
169-
onClick = {
170-
checkForUpdates(context, updateChecker) { info ->
171-
updateInfoForDialog = info
159+
if (org.jellyfin.androidtv.BuildConfig.ENABLE_OTA_UPDATES) {
160+
item {
161+
ListButton(
162+
leadingContent = {
163+
Icon(
164+
painterResource(R.drawable.ic_get_app),
165+
contentDescription = null
166+
)
167+
},
168+
headingContent = { Text("Check for Updates") },
169+
captionContent = { Text("Download latest Moonfin version") },
170+
onClick = {
171+
checkForUpdates(context, updateChecker) { info ->
172+
updateInfoForDialog = info
173+
}
172174
}
173-
}
174-
)
175-
}
175+
)
176+
}
176177

177-
item {
178-
var updateNotificationsEnabled by rememberPreference(userPreferences, UserPreferences.updateNotificationsEnabled)
179-
ListButton(
180-
headingContent = { Text("Update Notifications") },
181-
captionContent = { Text("Show notification on app launch when updates are available") },
182-
trailingContent = { Checkbox(checked = updateNotificationsEnabled) },
183-
onClick = { updateNotificationsEnabled = !updateNotificationsEnabled }
184-
)
178+
item {
179+
var updateNotificationsEnabled by rememberPreference(userPreferences, UserPreferences.updateNotificationsEnabled)
180+
ListButton(
181+
headingContent = { Text("Update Notifications") },
182+
captionContent = { Text("Show notification on app launch when updates are available") },
183+
trailingContent = { Checkbox(checked = updateNotificationsEnabled) },
184+
onClick = { updateNotificationsEnabled = !updateNotificationsEnabled }
185+
)
186+
}
185187
}
186188

187189
item {

app/src/main/res/values/strings.xml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -397,10 +397,10 @@
397397
<string name="user_picker_last_user_title">Most recently used</string>
398398
<string name="user_picker_last_user_summary">Use the last active account</string>
399399
<string name="lbl_switch_user">Switch account</string>
400-
<string name="no_network_permissions">Jellyfin requires network permissions to function</string>
400+
<string name="no_network_permissions">Moonfin requires network permissions to function</string>
401401
<string name="press_to_close">Press to close</string>
402402
<string name="saved_servers">Saved servers</string>
403-
<string name="welcome_title">Welcome to Jellyfin!</string>
403+
<string name="welcome_title">Welcome to Moonfin!</string>
404404
<string name="welcome_content">Connect to your server to get started.</string>
405405
<string name="no_user_warning">We couldn\'t find any accounts!\nAdd a new account by pressing the \"Add account\" button.</string>
406406
<string name="who_is_watching">Who\'s watching?</string>
@@ -458,7 +458,7 @@
458458
<string name="lbl_forgot_pin">Forgot PIN?</string>
459459
<string name="select_version">Version</string>
460460
<string name="licenses_link">Licenses</string>
461-
<string name="licenses_link_description">View the licenses of the libraries used by the Jellyfin app</string>
461+
<string name="licenses_link_description">View the licenses of the libraries used by the Moonfin app</string>
462462
<string name="license_description">Description</string>
463463
<string name="license_version">Version</string>
464464
<string name="license_artifact">Artifact</string>
@@ -500,7 +500,7 @@
500500
<string name="pref_home_favorites_button_summary">Toggle the favorites button in the top toolbar</string>
501501
<string name="pref_show_libraries_in_toolbar">Show library buttons in toolbar</string>
502502
<string name="pref_show_libraries_in_toolbar_description">Toggle library buttons in the top toolbar</string>
503-
<string name="searchable_hint">Search Jellyfin</string>
503+
<string name="searchable_hint">Search Moonfin</string>
504504
<string name="searchable_settings_description">Media</string>
505505
<string name="pref_subtitles_background_title">Enable background</string>
506506
<string name="pref_subtitles_background_summary">Show a black background behind the subtitles</string>
@@ -581,7 +581,7 @@
581581
<string name="crash_report_toast">Oops! Something went wrong, a crash report was sent to your Jellyfin server.</string>
582582
<string name="server_setup_incomplete">The setup of this server has not been completed. Open Jellyfin in a web browser to finish setup before signing in.</string>
583583
<string name="episode_name_special">special</string>
584-
<string name="app_notification_beta">App updated to %1$s\nThank you for participating in the Jellyfin beta program.</string>
584+
<string name="app_notification_beta">App updated to %1$s\nThank you for participating in the Moonfin beta program.</string>
585585
<string name="item_deleted">%1$s deleted</string>
586586
<string name="item_deletion_failed">Unable to delete %1$s</string>
587587
<string name="pref_enable_media_management">Enable media management</string>
@@ -702,7 +702,7 @@
702702
<string name="playback_video_player">Video player</string>
703703
<string name="video_player_internal">Built-in video player</string>
704704
<string name="video_player_external">External app</string>
705-
<string name="playback_video_player_external_empty">You don\'t have any video player apps installed on your device that can open Jellyfin videos.</string>
705+
<string name="playback_video_player_external_empty">You don\'t have any video player apps installed on your device that can open videos from your server.</string>
706706
<string name="filters">Filters</string>
707707
<plurals name="seconds">
708708
<item quantity="one">%1$s second</item>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
3+
xmlns:tools="http://schemas.android.com/tools">
4+
5+
<!-- Remove OTA install permission for Play Store builds -->
6+
<uses-permission
7+
android:name="android.permission.REQUEST_INSTALL_PACKAGES"
8+
tools:node="remove" />
9+
10+
</manifest>
Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
Your media, on your terms.
22

3-
The Jellyfin project is an open source, free software media server. No fees, no tracking, no hidden agenda. Get our free server to collect all your audio, video, photos, and more in one place.
3+
Moonfin is a feature-rich Android TV client for Jellyfin media servers. Enjoy your personal media library with a modern, polished interface designed for the big screen.
44

5-
To use the app, you must have a Jellyfin server set up and running. Find out more at <a href="https://jellyfin.org/" target="_blank">jellyfin.org</a>.
5+
To use Moonfin, you need a Jellyfin server set up and running. Jellyfin is free, open-source media server software. Learn more at <a href="https://jellyfin.org/" target="_blank">jellyfin.org</a>.
66

7-
With a Jellyfin server, you can:
7+
With Moonfin, you can:
88

9-
* Watch Live TV and recorded shows from your Jellyfin server (additional hardware/services required)
10-
* Stream to a Chromecast device on your network
11-
* Stream your media to your Android device
12-
* View your collection in an easy to use interface
9+
* Browse and stream your movies, TV shows, and music
10+
* Watch Live TV and recorded shows (additional server setup required)
11+
* Preview trailers before watching
12+
* Request new content via Jellyseerr integration
13+
* Enjoy a beautiful Compose-based interface with glass-morphism design
14+
* Customize your home screen and playback experience
15+
* Sync settings across devices with the Moonfin server plugin
1316

14-
This is the official Jellyfin companion app for Android TV. Thank you for using Jellyfin!
17+
Moonfin is a third-party client built with love for the Jellyfin community.

0 commit comments

Comments
 (0)