mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-07-19 02:34:17 +01:00
Merge remote-tracking branch 'origin/develop' into feat/android/choose-download-location
Signed-off-by: Lance Chant <13349722+lancechant@users.noreply.github.com>
This commit is contained in:
@@ -1,46 +1,20 @@
|
||||
plugins {
|
||||
id 'com.android.library'
|
||||
id 'kotlin-android'
|
||||
}
|
||||
apply plugin: 'expo-module-gradle-plugin'
|
||||
|
||||
group = 'expo.modules.backgrounddownloader'
|
||||
version = '1.0.0'
|
||||
|
||||
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
|
||||
def kotlinVersion = findProperty('android.kotlinVersion') ?: '1.9.25'
|
||||
|
||||
apply from: expoModulesCorePlugin
|
||||
|
||||
applyKotlinExpoModulesCorePlugin()
|
||||
useDefaultAndroidSdkVersions()
|
||||
useCoreDependencies()
|
||||
useExpoPublishing()
|
||||
expoModule {
|
||||
canBePublished false
|
||||
}
|
||||
|
||||
android {
|
||||
namespace "expo.modules.backgrounddownloader"
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_17
|
||||
targetCompatibility JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "17"
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
defaultConfig {
|
||||
versionCode 1
|
||||
versionName "1.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
|
||||
implementation "com.squareup.okhttp3:okhttp:5.3.0"
|
||||
implementation "com.squareup.okhttp3:okhttp:4.12.0"
|
||||
}
|
||||
|
||||
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
|
||||
kotlinOptions {
|
||||
jvmTarget = "17"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
|
||||
<application>
|
||||
<service
|
||||
android:name=".DownloadService"
|
||||
|
||||
@@ -5,44 +5,104 @@ import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.content.pm.ServiceInfo
|
||||
import android.os.Binder
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.os.PowerManager
|
||||
import android.os.SystemClock
|
||||
import android.util.Log
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.ServiceCompat
|
||||
|
||||
class DownloadService : Service() {
|
||||
private val TAG = "DownloadService"
|
||||
private val NOTIFICATION_ID = 1001
|
||||
private val CHANNEL_ID = "download_channel"
|
||||
|
||||
|
||||
// Time threshold to detect if we're in boot context (10 minutes after boot)
|
||||
private val BOOT_THRESHOLD_MS = 10 * 60 * 1000L
|
||||
|
||||
private val binder = DownloadServiceBinder()
|
||||
private var activeDownloadCount = 0
|
||||
private var currentDownloadTitle = "Preparing download..."
|
||||
private var currentProgress = 0
|
||||
|
||||
private var isForegroundStarted = false
|
||||
private var wakeLock: PowerManager.WakeLock? = null
|
||||
|
||||
inner class DownloadServiceBinder : Binder() {
|
||||
fun getService(): DownloadService = this@DownloadService
|
||||
}
|
||||
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
Log.d(TAG, "DownloadService created")
|
||||
createNotificationChannel()
|
||||
|
||||
val pm = getSystemService(PowerManager::class.java)
|
||||
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Streamyfin::DownloadWakeLock")
|
||||
wakeLock?.acquire()
|
||||
|
||||
Log.d(TAG, "Wake lock acquired")
|
||||
}
|
||||
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder {
|
||||
Log.d(TAG, "DownloadService bound")
|
||||
return binder
|
||||
}
|
||||
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
Log.d(TAG, "DownloadService started")
|
||||
startForeground(NOTIFICATION_ID, createNotification())
|
||||
|
||||
// On Android 15+, dataSync foreground services cannot be started from BOOT_COMPLETED context
|
||||
// Check if we're likely in a boot context and skip foreground start if so
|
||||
if (Build.VERSION.SDK_INT >= 35 && isLikelyBootContext()) {
|
||||
Log.w(TAG, "Skipping foreground start - likely boot context on Android 15+")
|
||||
stopSelf()
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
startForegroundSafely()
|
||||
return START_STICKY
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we're likely in a boot context by checking system uptime.
|
||||
* If the system has been up for less than the threshold, we might be in boot context.
|
||||
*/
|
||||
private fun isLikelyBootContext(): Boolean {
|
||||
val uptimeMs = SystemClock.elapsedRealtime()
|
||||
return uptimeMs < BOOT_THRESHOLD_MS
|
||||
}
|
||||
|
||||
/**
|
||||
* Start foreground service safely with proper service type for Android 14+
|
||||
*/
|
||||
private fun startForegroundSafely() {
|
||||
if (isForegroundStarted) return
|
||||
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= 34) {
|
||||
ServiceCompat.startForeground(
|
||||
this,
|
||||
NOTIFICATION_ID,
|
||||
createNotification(),
|
||||
ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
|
||||
)
|
||||
} else {
|
||||
startForeground(NOTIFICATION_ID, createNotification())
|
||||
}
|
||||
isForegroundStarted = true
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to start foreground service", e)
|
||||
// If we can't start foreground, stop the service
|
||||
stopSelf()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
wakeLock?.let { if (it.isHeld) it.release() }
|
||||
Log.d(TAG, "Wake lock released")
|
||||
Log.d(TAG, "DownloadService destroyed")
|
||||
super.onDestroy()
|
||||
}
|
||||
@@ -86,7 +146,7 @@ class DownloadService : Service() {
|
||||
activeDownloadCount++
|
||||
Log.d(TAG, "Download started, active count: $activeDownloadCount")
|
||||
if (activeDownloadCount == 1) {
|
||||
startForeground(NOTIFICATION_ID, createNotification())
|
||||
startForegroundSafely()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +154,10 @@ class DownloadService : Service() {
|
||||
activeDownloadCount = maxOf(0, activeDownloadCount - 1)
|
||||
Log.d(TAG, "Download stopped, active count: $activeDownloadCount")
|
||||
if (activeDownloadCount == 0) {
|
||||
stopForeground(STOP_FOREGROUND_REMOVE)
|
||||
if (isForegroundStarted) {
|
||||
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
|
||||
isForegroundStarted = false
|
||||
}
|
||||
stopSelf()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user