diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt index 2196da0d7..e61db1dfa 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationHelper.kt @@ -4,7 +4,6 @@ import android.app.NotificationManager import android.app.PendingIntent import android.content.Context import android.content.Intent -import android.graphics.BitmapFactory import android.net.Uri import android.os.Build import android.provider.Settings @@ -12,14 +11,11 @@ import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import androidx.core.content.ContextCompat import androidx.preference.PreferenceManager -import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers -import io.reactivex.rxjava3.core.Completable -import io.reactivex.rxjava3.core.Single -import io.reactivex.rxjava3.schedulers.Schedulers import org.schabi.newpipe.R import org.schabi.newpipe.extractor.stream.StreamInfoItem import org.schabi.newpipe.local.feed.service.FeedUpdateInfo import org.schabi.newpipe.util.NavigationHelper +import org.schabi.newpipe.util.PicassoHelper /** * Helper for everything related to show notifications about new streams to the user. @@ -34,7 +30,7 @@ class NotificationHelper(val context: Context) { * Show a notification about new streams from a single channel. * Opening the notification will open the corresponding channel page. */ - fun displayNewStreamsNotification(data: FeedUpdateInfo): Completable { + fun displayNewStreamsNotification(data: FeedUpdateInfo) { val newStreams: List = data.newStreams val summary = context.resources.getQuantityString( R.plurals.new_streams, newStreams.size, newStreams.size @@ -59,12 +55,6 @@ class NotificationHelper(val context: Context) { .setBadgeIconType(NotificationCompat.BADGE_ICON_LARGE) .setPriority(NotificationCompat.PRIORITY_DEFAULT) .setSmallIcon(R.drawable.ic_newpipe_triangle_white) - .setLargeIcon( - BitmapFactory.decodeResource( - context.resources, - R.drawable.ic_newpipe_triangle_white - ) - ) .setColor(ContextCompat.getColor(context, R.color.ic_launcher_background)) .setColorized(true) .setAutoCancel(true) @@ -87,19 +77,17 @@ class NotificationHelper(val context: Context) { NavigationHelper .getChannelIntent(context, data.listInfo.serviceId, data.listInfo.url) .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK), - 0 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) + PendingIntent.FLAG_IMMUTABLE + else + 0 ) ) - return Single.create(NotificationIcon(context, data.avatarUrl)) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .doOnSuccess { icon -> - builder.setLargeIcon(icon) - } - .ignoreElement() - .onErrorComplete() - .doOnComplete { manager.notify(data.pseudoId, builder.build()) } + PicassoHelper.loadNotificationIcon(data.avatarUrl, context) { bitmap -> + builder.setLargeIcon(bitmap) + manager.notify(data.pseudoId, builder.build()) + } } companion object { diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationIcon.kt b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationIcon.kt deleted file mode 100644 index 0fb6877a6..000000000 --- a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationIcon.kt +++ /dev/null @@ -1,60 +0,0 @@ -package org.schabi.newpipe.local.feed.notifications - -import android.app.ActivityManager -import android.content.Context -import android.graphics.Bitmap -import android.graphics.drawable.Drawable -import com.squareup.picasso.Picasso -import com.squareup.picasso.Target -import io.reactivex.rxjava3.core.SingleEmitter -import io.reactivex.rxjava3.core.SingleOnSubscribe -import org.schabi.newpipe.util.PicassoHelper - -/** - * Helper class to handle loading and resizing of icons - * which are used going to be used in notifications. - */ -internal class NotificationIcon( - context: Context, - private val url: String, -) : SingleOnSubscribe { - - private val size = getIconSize(context) - - override fun subscribe(emitter: SingleEmitter) { - val target = SingleEmitterTarget(emitter) - PicassoHelper.loadThumbnail(url) - .resize(size, size) - .centerCrop() - .into(target) - emitter.setCancellable { - PicassoHelper.cancelRequest(target) - } - } - - private class SingleEmitterTarget(private val emitter: SingleEmitter) : Target { - override fun onBitmapLoaded(bitmap: Bitmap, from: Picasso.LoadedFrom?) { - if (!emitter.isDisposed) { - emitter.onSuccess(bitmap) - } - } - - override fun onBitmapFailed(e: Exception, errorDrawable: Drawable?) { - emitter.tryOnError(e) - } - - override fun onPrepareLoad(placeHolderDrawable: Drawable?) = Unit - } - - private companion object { - - fun getIconSize(context: Context): Int { - val activityManager = context.getSystemService( - Context.ACTIVITY_SERVICE - ) as ActivityManager? - val size1 = activityManager?.launcherLargeIconSize ?: 0 - val size2 = context.resources.getDimensionPixelSize(android.R.dimen.app_icon_size) - return maxOf(size2, size1) - } - } -} diff --git a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt index 48525864b..365ba94d3 100644 --- a/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt +++ b/app/src/main/java/org/schabi/newpipe/local/feed/notifications/NotificationWorker.kt @@ -1,6 +1,7 @@ package org.schabi.newpipe.local.feed.notifications import android.content.Context +import android.util.Log import androidx.core.app.NotificationCompat import androidx.work.BackoffPolicy import androidx.work.Constraints @@ -12,7 +13,7 @@ import androidx.work.PeriodicWorkRequest import androidx.work.WorkManager import androidx.work.WorkerParameters import androidx.work.rxjava3.RxWorker -import io.reactivex.rxjava3.core.Observable +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Single import org.schabi.newpipe.App import org.schabi.newpipe.R @@ -34,30 +35,39 @@ class NotificationWorker( } private val feedLoadManager = FeedLoadManager(appContext) - override fun createWork(): Single = if (isEnabled(applicationContext)) { + override fun createWork(): Single = if (areNotificationsEnabled(applicationContext)) { feedLoadManager.startLoading( ignoreOutdatedThreshold = true, groupId = FeedLoadManager.GROUP_NOTIFICATION_ENABLED ) + .doOnSubscribe { showLoadingFeedForegroundNotification() } .map { feed -> - feed.mapNotNull { x -> - x.value?.takeIf { - it.newStreams.isNotEmpty() + // filter out feedUpdateInfo items (i.e. channels) with nothing new + feed.mapNotNull { + it.value?.takeIf { feedUpdateInfo -> + feedUpdateInfo.newStreams.isNotEmpty() } } } - .doOnSubscribe { setForegroundAsync(createForegroundInfo()) } - .flatMapObservable { Observable.fromIterable(it) } - .flatMapCompletable { x -> notificationHelper.displayNewStreamsNotification(x) } - .toSingleDefault(Result.success()) + .observeOn(AndroidSchedulers.mainThread()) // Picasso requires calls from main thread + .map { feedUpdateInfoList -> + // display notifications for each feedUpdateInfo (i.e. channel) + feedUpdateInfoList.forEach { feedUpdateInfo -> + notificationHelper.displayNewStreamsNotification(feedUpdateInfo) + } + return@map Result.success() + } + .doOnError { throwable -> + Log.e(TAG, "Error while displaying streams notifications", throwable) + // TODO show error notification + } .onErrorReturnItem(Result.failure()) } else { - // Can be the case when the user disables notifications for NewPipe - // in the device's app settings. + // the user can disable streams notifications in the device's app settings Single.just(Result.success()) } - private fun createForegroundInfo(): ForegroundInfo { + private fun showLoadingFeedForegroundNotification() { val notification = NotificationCompat.Builder( applicationContext, applicationContext.getString(R.string.notification_channel_id) @@ -68,14 +78,15 @@ class NotificationWorker( .setPriority(NotificationCompat.PRIORITY_LOW) .setContentTitle(applicationContext.getString(R.string.feed_notification_loading)) .build() - return ForegroundInfo(FeedLoadService.NOTIFICATION_ID, notification) + setForegroundAsync(ForegroundInfo(FeedLoadService.NOTIFICATION_ID, notification)) } companion object { - private const val TAG = App.PACKAGE_NAME + "_streams_notifications" + private val TAG = NotificationWorker::class.java.simpleName + private const val WORK_TAG = App.PACKAGE_NAME + "_streams_notifications" - private fun isEnabled(context: Context) = + private fun areNotificationsEnabled(context: Context) = NotificationHelper.areNewStreamsNotificationsEnabled(context) && NotificationHelper.areNotificationsEnabledOnDevice(context) @@ -86,7 +97,7 @@ class NotificationWorker( */ @JvmStatic fun initialize(context: Context) { - if (isEnabled(context)) { + if (areNotificationsEnabled(context)) { schedule(context) } else { cancel(context) @@ -114,13 +125,13 @@ class NotificationWorker( options.interval, TimeUnit.MILLISECONDS ).setConstraints(constraints) - .addTag(TAG) + .addTag(WORK_TAG) .setBackoffCriteria(BackoffPolicy.LINEAR, 30, TimeUnit.MINUTES) .build() WorkManager.getInstance(context) .enqueueUniquePeriodicWork( - TAG, + WORK_TAG, if (force) { ExistingPeriodicWorkPolicy.REPLACE } else { @@ -139,7 +150,7 @@ class NotificationWorker( @JvmStatic fun runNow(context: Context) { val request = OneTimeWorkRequestBuilder() - .addTag(TAG) + .addTag(WORK_TAG) .build() WorkManager.getInstance(context).enqueue(request) } @@ -149,7 +160,7 @@ class NotificationWorker( */ @JvmStatic fun cancel(context: Context) { - WorkManager.getInstance(context).cancelAllWorkByTag(TAG) + WorkManager.getInstance(context).cancelAllWorkByTag(WORK_TAG) } } } diff --git a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java index efacd1fc2..d5552ae65 100644 --- a/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/PicassoHelper.java @@ -5,6 +5,8 @@ import static org.schabi.newpipe.extractor.utils.Utils.isBlank; import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.drawable.Drawable; import com.squareup.picasso.Cache; import com.squareup.picasso.LruCache; @@ -19,6 +21,7 @@ import org.schabi.newpipe.R; import java.io.File; import java.io.IOException; import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; import okhttp3.OkHttpClient; @@ -161,6 +164,29 @@ public final class PicassoHelper { } + public static void loadNotificationIcon(final String url, + final Context context, + final Consumer bitmapConsumer) { + loadImageDefault(url, R.drawable.ic_newpipe_triangle_white) + .into(new Target() { + @Override + public void onBitmapLoaded(final Bitmap bitmap, final Picasso.LoadedFrom from) { + bitmapConsumer.accept(bitmap); + } + + @Override + public void onBitmapFailed(final Exception e, final Drawable errorDrawable) { + bitmapConsumer.accept(BitmapFactory.decodeResource(context.getResources(), + R.drawable.ic_newpipe_triangle_white)); + } + + @Override + public void onPrepareLoad(final Drawable placeHolderDrawable) { + } + }); + } + + private static RequestCreator loadImageDefault(final String url, final int placeholderResId) { if (!shouldLoadImages || isBlank(url)) { return picassoInstance