ultrasonic-app-subsonic-and.../ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/DownloadService.kt

226 lines
7.5 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* MediaPlayerService.kt
* Copyright (C) 2009-2021 Ultrasonic developers
*
* Distributed under terms of the GNU GPLv3 license.
*/
package org.moire.ultrasonic.service
import android.annotation.SuppressLint
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
import android.content.Intent
import android.os.Build
import android.os.IBinder
import android.support.v4.media.session.MediaSessionCompat
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import org.koin.android.ext.android.inject
import org.moire.ultrasonic.R
import org.moire.ultrasonic.activity.NavigationActivity
import org.moire.ultrasonic.app.UApp
import org.moire.ultrasonic.util.Constants
import org.moire.ultrasonic.util.SimpleServiceBinder
import org.moire.ultrasonic.util.Util
import timber.log.Timber
/**
* Android Foreground service which is used to download tracks even when the app is not visible
*
* "A foreground service is a service that the user is
* actively aware of and isnt a candidate for the system to kill when low on memory."
*
* TODO: Migrate this to use the Media3 DownloadHelper
*/
class DownloadService : Service() {
private val binder: IBinder = SimpleServiceBinder(this)
private val downloader by inject<Downloader>()
private var mediaSession: MediaSessionCompat? = null
private var isInForeground = false
override fun onBind(intent: Intent): IBinder {
return binder
}
override fun onCreate() {
super.onCreate()
// Create Notification Channel
createNotificationChannel()
updateNotification()
instance = this
Timber.i("DownloadService created")
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
return START_NOT_STICKY
}
override fun onDestroy() {
super.onDestroy()
instance = null
try {
downloader.stop()
mediaSession?.release()
mediaSession = null
} catch (ignored: Throwable) {
}
Timber.i("DownloadService stopped")
}
fun notifyDownloaderStopped() {
isInForeground = false
stopForeground(true)
stopSelf()
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// The suggested importance of a startForeground service notification is IMPORTANCE_LOW
val channel = NotificationChannel(
NOTIFICATION_CHANNEL_ID,
NOTIFICATION_CHANNEL_NAME,
NotificationManager.IMPORTANCE_LOW
)
channel.lightColor = android.R.color.holo_blue_dark
channel.lockscreenVisibility = Notification.VISIBILITY_PUBLIC
channel.setShowBadge(false)
val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
manager.createNotificationChannel(channel)
}
}
// We should use a single notification builder, otherwise the notification may not be updated
// Set some values that never change
private val notificationBuilder: NotificationCompat.Builder by lazy {
NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_stat_ultrasonic)
.setAutoCancel(false)
.setOngoing(true)
.setOnlyAlertOnce(true)
.setWhen(System.currentTimeMillis())
.setShowWhen(false)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setContentIntent(getPendingIntentForContent())
.setPriority(NotificationCompat.PRIORITY_LOW)
}
private fun updateNotification() {
val notification = buildForegroundNotification()
if (isInForeground) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
manager.notify(NOTIFICATION_ID, notification)
} else {
val manager = NotificationManagerCompat.from(this)
manager.notify(NOTIFICATION_ID, notification)
}
Timber.v("Updated notification")
} else {
startForeground(NOTIFICATION_ID, notification)
isInForeground = true
Timber.v("Created Foreground notification")
}
}
/**
* This method builds a notification, reusing the Notification Builder if possible
*/
@Suppress("SpreadOperator")
private fun buildForegroundNotification(): Notification {
if (downloader.started) {
// No song is playing, but Ultrasonic is downloading files
notificationBuilder.setContentTitle(
getString(R.string.notification_downloading_title)
)
}
return notificationBuilder.build()
}
@SuppressLint("UnspecifiedImmutableFlag")
private fun getPendingIntentForContent(): PendingIntent {
val intent = Intent(this, NavigationActivity::class.java)
.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
val flags = PendingIntent.FLAG_UPDATE_CURRENT
intent.putExtra(Constants.INTENT_SHOW_PLAYER, true)
return PendingIntent.getActivity(this, 0, intent, flags)
}
@Suppress("MagicNumber")
companion object {
private const val NOTIFICATION_CHANNEL_ID = "org.moire.ultrasonic"
private const val NOTIFICATION_CHANNEL_NAME = "Ultrasonic background service"
private const val NOTIFICATION_ID = 3033
@Volatile
private var instance: DownloadService? = null
private val instanceLock = Any()
@JvmStatic
fun getInstance(): DownloadService? {
val context = UApp.applicationContext()
// Try for twenty times to retrieve a running service,
// sleep 100 millis between each try,
// and run the block that creates a service only synchronized.
for (i in 0..19) {
if (instance != null) return instance
synchronized(instanceLock) {
if (instance != null) return instance
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(
Intent(context, DownloadService::class.java)
)
} else {
context.startService(Intent(context, DownloadService::class.java))
}
}
Util.sleepQuietly(100L)
}
return instance
}
@JvmStatic
val runningInstance: DownloadService?
get() {
synchronized(instanceLock) { return instance }
}
@JvmStatic
fun executeOnStartedMediaPlayerService(
taskToExecute: (DownloadService) -> Unit
) {
val t: Thread = object : Thread() {
override fun run() {
val instance = getInstance()
if (instance == null) {
Timber.e("ExecuteOnStarted.. failed to get a DownloadService instance!")
return
} else {
taskToExecute(instance)
}
}
}
t.start()
}
}
}