mirror of https://github.com/TeamNewPipe/NewPipe
215 lines
8.3 KiB
Kotlin
215 lines
8.3 KiB
Kotlin
/*
|
|
* Copyright 2019 Mauricio Colli <mauriciocolli@outlook.com>
|
|
* FeedLoadService.kt is part of NewPipe
|
|
*
|
|
* License: GPL-3.0+
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
package org.schabi.newpipe.local.feed.service
|
|
|
|
import android.app.Service
|
|
import android.content.BroadcastReceiver
|
|
import android.content.Context
|
|
import android.content.Intent
|
|
import android.content.IntentFilter
|
|
import android.os.Build
|
|
import android.os.IBinder
|
|
import android.util.Log
|
|
import androidx.core.app.NotificationCompat
|
|
import androidx.core.app.NotificationManagerCompat
|
|
import androidx.core.app.PendingIntentCompat
|
|
import androidx.core.app.ServiceCompat
|
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
|
import io.reactivex.rxjava3.core.Flowable
|
|
import io.reactivex.rxjava3.disposables.Disposable
|
|
import io.reactivex.rxjava3.functions.Function
|
|
import org.schabi.newpipe.App
|
|
import org.schabi.newpipe.MainActivity.DEBUG
|
|
import org.schabi.newpipe.R
|
|
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
|
import org.schabi.newpipe.local.feed.service.FeedEventManager.Event.ErrorResultEvent
|
|
import org.schabi.newpipe.local.feed.service.FeedEventManager.postEvent
|
|
import java.util.concurrent.TimeUnit
|
|
|
|
class FeedLoadService : Service() {
|
|
companion object {
|
|
private val TAG = FeedLoadService::class.java.simpleName
|
|
const val NOTIFICATION_ID = 7293450
|
|
private const val ACTION_CANCEL = App.PACKAGE_NAME + ".local.feed.service.FeedLoadService.CANCEL"
|
|
|
|
/**
|
|
* How often the notification will be updated.
|
|
*/
|
|
private const val NOTIFICATION_SAMPLING_PERIOD = 1500
|
|
|
|
const val EXTRA_GROUP_ID: String = "FeedLoadService.EXTRA_GROUP_ID"
|
|
}
|
|
|
|
private var loadingDisposable: Disposable? = null
|
|
private var notificationDisposable: Disposable? = null
|
|
|
|
private lateinit var feedLoadManager: FeedLoadManager
|
|
|
|
// /////////////////////////////////////////////////////////////////////////
|
|
// Lifecycle
|
|
// /////////////////////////////////////////////////////////////////////////
|
|
|
|
override fun onCreate() {
|
|
super.onCreate()
|
|
feedLoadManager = FeedLoadManager(this)
|
|
}
|
|
|
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
|
if (DEBUG) {
|
|
Log.d(
|
|
TAG,
|
|
"onStartCommand() called with: intent = [" + intent + "]," +
|
|
" flags = [" + flags + "], startId = [" + startId + "]"
|
|
)
|
|
}
|
|
|
|
if (intent == null || loadingDisposable != null) {
|
|
return START_NOT_STICKY
|
|
}
|
|
|
|
setupNotification()
|
|
setupBroadcastReceiver()
|
|
|
|
val groupId = intent.getLongExtra(EXTRA_GROUP_ID, FeedGroupEntity.GROUP_ALL_ID)
|
|
loadingDisposable = feedLoadManager.startLoading(groupId)
|
|
.observeOn(AndroidSchedulers.mainThread())
|
|
.doOnSubscribe {
|
|
startForeground(NOTIFICATION_ID, notificationBuilder.build())
|
|
}
|
|
.subscribe { _, error: Throwable? -> // explicitly mark error as nullable
|
|
if (error != null) {
|
|
Log.e(TAG, "Error while storing result", error)
|
|
handleError(error)
|
|
return@subscribe
|
|
}
|
|
stopService()
|
|
}
|
|
return START_NOT_STICKY
|
|
}
|
|
|
|
private fun disposeAll() {
|
|
unregisterReceiver(broadcastReceiver)
|
|
loadingDisposable?.dispose()
|
|
notificationDisposable?.dispose()
|
|
}
|
|
|
|
private fun stopService() {
|
|
disposeAll()
|
|
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
|
|
stopSelf()
|
|
}
|
|
|
|
override fun onBind(intent: Intent): IBinder? {
|
|
return null
|
|
}
|
|
|
|
// /////////////////////////////////////////////////////////////////////////
|
|
// Loading & Handling
|
|
// /////////////////////////////////////////////////////////////////////////
|
|
|
|
class RequestException(val subscriptionId: Long, message: String, cause: Throwable) : Exception(message, cause)
|
|
|
|
// /////////////////////////////////////////////////////////////////////////
|
|
// Notification
|
|
// /////////////////////////////////////////////////////////////////////////
|
|
|
|
private lateinit var notificationManager: NotificationManagerCompat
|
|
private lateinit var notificationBuilder: NotificationCompat.Builder
|
|
|
|
private fun createNotification(): NotificationCompat.Builder {
|
|
val cancelActionIntent = PendingIntentCompat
|
|
.getBroadcast(this, NOTIFICATION_ID, Intent(ACTION_CANCEL), 0, false)
|
|
|
|
return NotificationCompat.Builder(this, getString(R.string.notification_channel_id))
|
|
.setOngoing(true)
|
|
.setProgress(-1, -1, true)
|
|
.setSmallIcon(R.drawable.ic_newpipe_triangle_white)
|
|
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
|
.addAction(0, getString(R.string.cancel), cancelActionIntent)
|
|
.setContentTitle(getString(R.string.feed_notification_loading))
|
|
}
|
|
|
|
private fun setupNotification() {
|
|
notificationManager = NotificationManagerCompat.from(this)
|
|
notificationBuilder = createNotification()
|
|
|
|
val throttleAfterFirstEmission = Function { flow: Flowable<FeedLoadState> ->
|
|
flow.take(1).concatWith(flow.skip(1).throttleLatest(NOTIFICATION_SAMPLING_PERIOD.toLong(), TimeUnit.MILLISECONDS))
|
|
}
|
|
|
|
notificationDisposable = feedLoadManager.notification
|
|
.publish(throttleAfterFirstEmission)
|
|
.observeOn(AndroidSchedulers.mainThread())
|
|
.doOnTerminate { notificationManager.cancel(NOTIFICATION_ID) }
|
|
.subscribe(this::updateNotificationProgress)
|
|
}
|
|
|
|
private fun updateNotificationProgress(state: FeedLoadState) {
|
|
notificationBuilder.setProgress(state.maxProgress, state.currentProgress, state.maxProgress == -1)
|
|
|
|
if (state.maxProgress == -1) {
|
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) notificationBuilder.setContentInfo(null)
|
|
if (state.updateDescription.isNotEmpty()) notificationBuilder.setContentText(state.updateDescription)
|
|
notificationBuilder.setContentText(state.updateDescription)
|
|
} else {
|
|
val progressText = state.currentProgress.toString() + "/" + state.maxProgress
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
|
if (state.updateDescription.isNotEmpty()) {
|
|
notificationBuilder.setContentText("${state.updateDescription} ($progressText)")
|
|
}
|
|
} else {
|
|
notificationBuilder.setContentInfo(progressText)
|
|
if (state.updateDescription.isNotEmpty()) {
|
|
notificationBuilder.setContentText(state.updateDescription)
|
|
}
|
|
}
|
|
}
|
|
|
|
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build())
|
|
}
|
|
|
|
// /////////////////////////////////////////////////////////////////////////
|
|
// Notification Actions
|
|
// /////////////////////////////////////////////////////////////////////////
|
|
|
|
private lateinit var broadcastReceiver: BroadcastReceiver
|
|
|
|
private fun setupBroadcastReceiver() {
|
|
broadcastReceiver = object : BroadcastReceiver() {
|
|
override fun onReceive(context: Context?, intent: Intent?) {
|
|
if (intent?.action == ACTION_CANCEL) {
|
|
feedLoadManager.cancel()
|
|
}
|
|
}
|
|
}
|
|
registerReceiver(broadcastReceiver, IntentFilter(ACTION_CANCEL))
|
|
}
|
|
|
|
// /////////////////////////////////////////////////////////////////////////
|
|
// Error handling
|
|
// /////////////////////////////////////////////////////////////////////////
|
|
|
|
private fun handleError(error: Throwable) {
|
|
postEvent(ErrorResultEvent(error))
|
|
stopService()
|
|
}
|
|
}
|