From e3981ec48404ee440dd4ac2cd53ee90e0785300a Mon Sep 17 00:00:00 2001 From: olly Date: Tue, 5 Feb 2019 17:45:39 +0000 Subject: [PATCH] Fix notifications to avoid flicker on KitKat On KitKat you need to reuse the same notification builder when generating a notification that's intended to replace a previous one. See: https://stackoverflow.com/questions/6406730/updating-an-ongoing-notification-quietly PiperOrigin-RevId: 232503682 --- RELEASENOTES.md | 4 + .../exoplayer2/demo/DemoDownloadService.java | 27 ++- .../ui/DownloadNotificationHelper.java | 171 ++++++++++++++++++ .../ui/DownloadNotificationUtil.java | 100 ++-------- .../ui/PlayerNotificationManager.java | 143 ++++++++------- 5 files changed, 284 insertions(+), 161 deletions(-) create mode 100644 library/ui/src/main/java/com/google/android/exoplayer2/ui/DownloadNotificationHelper.java diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 817f9f2645..1278d36600 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -43,6 +43,10 @@ and `useSurfaceYuvOutput`. * Change signature of `PlayerNotificationManager.NotificationListener` to better fit service requirements. Remove ability to set a custom stop action. +* Fix issues with flickering notifications on KitKat. + `PlayerNotificationManager` has been fixed. Apps using + `DownloadNotificationUtil` should switch to using + `DownloadNotificationHelper`. ### 2.9.5 ### diff --git a/demos/main/src/main/java/com/google/android/exoplayer2/demo/DemoDownloadService.java b/demos/main/src/main/java/com/google/android/exoplayer2/demo/DemoDownloadService.java index dcccd884ec..91e2aa5bcc 100644 --- a/demos/main/src/main/java/com/google/android/exoplayer2/demo/DemoDownloadService.java +++ b/demos/main/src/main/java/com/google/android/exoplayer2/demo/DemoDownloadService.java @@ -20,7 +20,7 @@ import com.google.android.exoplayer2.offline.DownloadManager; import com.google.android.exoplayer2.offline.DownloadService; import com.google.android.exoplayer2.offline.DownloadState; import com.google.android.exoplayer2.scheduler.PlatformScheduler; -import com.google.android.exoplayer2.ui.DownloadNotificationUtil; +import com.google.android.exoplayer2.ui.DownloadNotificationHelper; import com.google.android.exoplayer2.util.NotificationUtil; import com.google.android.exoplayer2.util.Util; @@ -33,6 +33,8 @@ public class DemoDownloadService extends DownloadService { private static int nextNotificationId = FOREGROUND_NOTIFICATION_ID + 1; + private DownloadNotificationHelper notificationHelper; + public DemoDownloadService() { super( FOREGROUND_NOTIFICATION_ID, @@ -42,6 +44,12 @@ public class DemoDownloadService extends DownloadService { nextNotificationId = FOREGROUND_NOTIFICATION_ID + 1; } + @Override + public void onCreate() { + super.onCreate(); + notificationHelper = new DownloadNotificationHelper(this, CHANNEL_ID); + } + @Override protected DownloadManager getDownloadManager() { return ((DemoApplication) getApplication()).getDownloadManager(); @@ -54,13 +62,8 @@ public class DemoDownloadService extends DownloadService { @Override protected Notification getForegroundNotification(DownloadState[] downloadStates) { - return DownloadNotificationUtil.buildProgressNotification( - /* context= */ this, - R.drawable.ic_download, - CHANNEL_ID, - /* contentIntent= */ null, - /* message= */ null, - downloadStates); + return notificationHelper.buildProgressNotification( + R.drawable.ic_download, /* contentIntent= */ null, /* message= */ null, downloadStates); } @Override @@ -68,18 +71,14 @@ public class DemoDownloadService extends DownloadService { Notification notification; if (downloadState.state == DownloadState.STATE_COMPLETED) { notification = - DownloadNotificationUtil.buildDownloadCompletedNotification( - /* context= */ this, + notificationHelper.buildDownloadCompletedNotification( R.drawable.ic_download_done, - CHANNEL_ID, /* contentIntent= */ null, Util.fromUtf8Bytes(downloadState.customMetadata)); } else if (downloadState.state == DownloadState.STATE_FAILED) { notification = - DownloadNotificationUtil.buildDownloadFailedNotification( - /* context= */ this, + notificationHelper.buildDownloadFailedNotification( R.drawable.ic_download_done, - CHANNEL_ID, /* contentIntent= */ null, Util.fromUtf8Bytes(downloadState.customMetadata)); } else { diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/DownloadNotificationHelper.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/DownloadNotificationHelper.java new file mode 100644 index 0000000000..b65c4fca40 --- /dev/null +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/DownloadNotificationHelper.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer2.ui; + +import android.app.Notification; +import android.app.PendingIntent; +import android.content.Context; +import android.support.annotation.DrawableRes; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; +import android.support.v4.app.NotificationCompat; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.offline.DownloadState; + +/** Helper for creating download notifications. */ +public final class DownloadNotificationHelper { + + private static final @StringRes int NULL_STRING_ID = 0; + + private final Context context; + private final NotificationCompat.Builder notificationBuilder; + + /** + * @param context A context. + * @param channelId The id of the notification channel to use. + */ + public DownloadNotificationHelper(Context context, String channelId) { + context = context.getApplicationContext(); + this.context = context; + this.notificationBuilder = new NotificationCompat.Builder(context, channelId); + } + + /** + * Returns a progress notification for the given download states. + * + * @param smallIcon A small icon for the notification. + * @param contentIntent An optional content intent to send when the notification is clicked. + * @param message An optional message to display on the notification. + * @param downloadStates The download states. + * @return The notification. + */ + public Notification buildProgressNotification( + @DrawableRes int smallIcon, + @Nullable PendingIntent contentIntent, + @Nullable String message, + DownloadState[] downloadStates) { + float totalPercentage = 0; + int downloadTaskCount = 0; + boolean allDownloadPercentagesUnknown = true; + boolean haveDownloadedBytes = false; + boolean haveDownloadTasks = false; + boolean haveRemoveTasks = false; + for (DownloadState downloadState : downloadStates) { + if (downloadState.state == DownloadState.STATE_REMOVING + || downloadState.state == DownloadState.STATE_RESTARTING + || downloadState.state == DownloadState.STATE_REMOVED) { + haveRemoveTasks = true; + continue; + } + if (downloadState.state != DownloadState.STATE_DOWNLOADING + && downloadState.state != DownloadState.STATE_COMPLETED) { + continue; + } + haveDownloadTasks = true; + if (downloadState.downloadPercentage != C.PERCENTAGE_UNSET) { + allDownloadPercentagesUnknown = false; + totalPercentage += downloadState.downloadPercentage; + } + haveDownloadedBytes |= downloadState.downloadedBytes > 0; + downloadTaskCount++; + } + + int titleStringId = + haveDownloadTasks + ? R.string.exo_download_downloading + : (haveRemoveTasks ? R.string.exo_download_removing : NULL_STRING_ID); + int progress = 0; + boolean indeterminate = true; + if (haveDownloadTasks) { + progress = (int) (totalPercentage / downloadTaskCount); + indeterminate = allDownloadPercentagesUnknown && haveDownloadedBytes; + } + return buildNotification( + smallIcon, + contentIntent, + message, + titleStringId, + progress, + indeterminate, + /* ongoing= */ true, + /* showWhen= */ false); + } + + /** + * Returns a notification for a completed download. + * + * @param smallIcon A small icon for the notifications. + * @param contentIntent An optional content intent to send when the notification is clicked. + * @param message An optional message to display on the notification. + * @return The notification. + */ + public Notification buildDownloadCompletedNotification( + @DrawableRes int smallIcon, @Nullable PendingIntent contentIntent, @Nullable String message) { + int titleStringId = R.string.exo_download_completed; + return buildEndStateNotification(smallIcon, contentIntent, message, titleStringId); + } + + /** + * Returns a notification for a failed download. + * + * @param smallIcon A small icon for the notifications. + * @param contentIntent An optional content intent to send when the notification is clicked. + * @param message An optional message to display on the notification. + * @return The notification. + */ + public Notification buildDownloadFailedNotification( + @DrawableRes int smallIcon, @Nullable PendingIntent contentIntent, @Nullable String message) { + @StringRes int titleStringId = R.string.exo_download_failed; + return buildEndStateNotification(smallIcon, contentIntent, message, titleStringId); + } + + private Notification buildEndStateNotification( + @DrawableRes int smallIcon, + @Nullable PendingIntent contentIntent, + @Nullable String message, + @StringRes int titleStringId) { + return buildNotification( + smallIcon, + contentIntent, + message, + titleStringId, + /* progress= */ 0, + /* indeterminateProgress= */ false, + /* ongoing= */ false, + /* showWhen= */ true); + } + + private Notification buildNotification( + @DrawableRes int smallIcon, + @Nullable PendingIntent contentIntent, + @Nullable String message, + @StringRes int titleStringId, + int progress, + boolean indeterminateProgress, + boolean ongoing, + boolean showWhen) { + notificationBuilder.setSmallIcon(smallIcon); + notificationBuilder.setContentTitle( + titleStringId == NULL_STRING_ID ? null : context.getResources().getString(titleStringId)); + notificationBuilder.setContentIntent(contentIntent); + notificationBuilder.setStyle( + message == null ? null : new NotificationCompat.BigTextStyle().bigText(message)); + notificationBuilder.setProgress(/* max= */ 100, progress, indeterminateProgress); + notificationBuilder.setOngoing(ongoing); + notificationBuilder.setShowWhen(showWhen); + return notificationBuilder.build(); + } +} diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/DownloadNotificationUtil.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/DownloadNotificationUtil.java index 2d1656af57..b9e952e62f 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/DownloadNotificationUtil.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/DownloadNotificationUtil.java @@ -20,16 +20,16 @@ import android.app.PendingIntent; import android.content.Context; import android.support.annotation.DrawableRes; import android.support.annotation.Nullable; -import android.support.annotation.StringRes; -import android.support.v4.app.NotificationCompat; -import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.offline.DownloadState; +import com.google.android.exoplayer2.util.Util; -/** Helper for creating download notifications. */ +/** + * @deprecated Using this class can cause notifications to flicker on devices with {@link + * Util#SDK_INT} < 21. Use {@link DownloadNotificationHelper} instead. + */ +@Deprecated public final class DownloadNotificationUtil { - private static final @StringRes int NULL_STRING_ID = 0; - private DownloadNotificationUtil() {} /** @@ -37,8 +37,7 @@ public final class DownloadNotificationUtil { * * @param context A context for accessing resources. * @param smallIcon A small icon for the notification. - * @param channelId The id of the notification channel to use. Only required for API level 26 and - * above. + * @param channelId The id of the notification channel to use. * @param contentIntent An optional content intent to send when the notification is clicked. * @param message An optional message to display on the notification. * @param downloadStates The download states. @@ -51,50 +50,8 @@ public final class DownloadNotificationUtil { @Nullable PendingIntent contentIntent, @Nullable String message, DownloadState[] downloadStates) { - float totalPercentage = 0; - int downloadTaskCount = 0; - boolean allDownloadPercentagesUnknown = true; - boolean haveDownloadedBytes = false; - boolean haveDownloadTasks = false; - boolean haveRemoveTasks = false; - for (DownloadState downloadState : downloadStates) { - if (downloadState.state == DownloadState.STATE_REMOVING - || downloadState.state == DownloadState.STATE_RESTARTING - || downloadState.state == DownloadState.STATE_REMOVED) { - haveRemoveTasks = true; - continue; - } - if (downloadState.state != DownloadState.STATE_DOWNLOADING - && downloadState.state != DownloadState.STATE_COMPLETED) { - continue; - } - haveDownloadTasks = true; - if (downloadState.downloadPercentage != C.PERCENTAGE_UNSET) { - allDownloadPercentagesUnknown = false; - totalPercentage += downloadState.downloadPercentage; - } - haveDownloadedBytes |= downloadState.downloadedBytes > 0; - downloadTaskCount++; - } - - int titleStringId = - haveDownloadTasks - ? R.string.exo_download_downloading - : (haveRemoveTasks ? R.string.exo_download_removing : NULL_STRING_ID); - NotificationCompat.Builder notificationBuilder = - newNotificationBuilder( - context, smallIcon, channelId, contentIntent, message, titleStringId); - - int progress = 0; - boolean indeterminate = true; - if (haveDownloadTasks) { - progress = (int) (totalPercentage / downloadTaskCount); - indeterminate = allDownloadPercentagesUnknown && haveDownloadedBytes; - } - notificationBuilder.setProgress(/* max= */ 100, progress, indeterminate); - notificationBuilder.setOngoing(true); - notificationBuilder.setShowWhen(false); - return notificationBuilder.build(); + return new DownloadNotificationHelper(context, channelId) + .buildProgressNotification(smallIcon, contentIntent, message, downloadStates); } /** @@ -102,8 +59,7 @@ public final class DownloadNotificationUtil { * * @param context A context for accessing resources. * @param smallIcon A small icon for the notifications. - * @param channelId The id of the notification channel to use. Only required for API level 26 and - * above. + * @param channelId The id of the notification channel to use. * @param contentIntent An optional content intent to send when the notification is clicked. * @param message An optional message to display on the notification. * @return The notification. @@ -114,10 +70,8 @@ public final class DownloadNotificationUtil { String channelId, @Nullable PendingIntent contentIntent, @Nullable String message) { - int titleStringId = R.string.exo_download_completed; - return newNotificationBuilder( - context, smallIcon, channelId, contentIntent, message, titleStringId) - .build(); + return new DownloadNotificationHelper(context, channelId) + .buildDownloadCompletedNotification(smallIcon, contentIntent, message); } /** @@ -125,8 +79,7 @@ public final class DownloadNotificationUtil { * * @param context A context for accessing resources. * @param smallIcon A small icon for the notifications. - * @param channelId The id of the notification channel to use. Only required for API level 26 and - * above. + * @param channelId The id of the notification channel to use. * @param contentIntent An optional content intent to send when the notification is clicked. * @param message An optional message to display on the notification. * @return The notification. @@ -137,30 +90,7 @@ public final class DownloadNotificationUtil { String channelId, @Nullable PendingIntent contentIntent, @Nullable String message) { - @StringRes int titleStringId = R.string.exo_download_failed; - return newNotificationBuilder( - context, smallIcon, channelId, contentIntent, message, titleStringId) - .build(); - } - - private static NotificationCompat.Builder newNotificationBuilder( - Context context, - @DrawableRes int smallIcon, - String channelId, - @Nullable PendingIntent contentIntent, - @Nullable String message, - @StringRes int titleStringId) { - NotificationCompat.Builder notificationBuilder = - new NotificationCompat.Builder(context, channelId).setSmallIcon(smallIcon); - if (titleStringId != NULL_STRING_ID) { - notificationBuilder.setContentTitle(context.getResources().getString(titleStringId)); - } - if (contentIntent != null) { - notificationBuilder.setContentIntent(contentIntent); - } - if (message != null) { - notificationBuilder.setStyle(new NotificationCompat.BigTextStyle().bigText(message)); - } - return notificationBuilder; + return new DownloadNotificationHelper(context, channelId) + .buildDownloadFailedNotification(smallIcon, contentIntent, message); } } diff --git a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java index 597f0dbd40..6634fdf820 100644 --- a/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java +++ b/library/ui/src/main/java/com/google/android/exoplayer2/ui/PlayerNotificationManager.java @@ -338,6 +338,7 @@ public class PlayerNotificationManager { private final Context context; private final String channelId; + private final NotificationCompat.Builder builder; private final int notificationId; private final MediaDescriptionAdapter mediaDescriptionAdapter; private final @Nullable CustomActionReceiver customActionReceiver; @@ -530,12 +531,14 @@ public class PlayerNotificationManager { MediaDescriptionAdapter mediaDescriptionAdapter, @Nullable NotificationListener notificationListener, @Nullable CustomActionReceiver customActionReceiver) { - this.context = context.getApplicationContext(); + context = context.getApplicationContext(); + this.context = context; this.channelId = channelId; this.notificationId = notificationId; this.mediaDescriptionAdapter = mediaDescriptionAdapter; this.notificationListener = notificationListener; this.customActionReceiver = customActionReceiver; + builder = new NotificationCompat.Builder(context, channelId); controlDispatcher = new DefaultControlDispatcher(); window = new Timeline.Window(); instanceId = instanceIdCounter++; @@ -887,7 +890,7 @@ public class PlayerNotificationManager { private Notification startOrUpdateNotification(@Nullable Bitmap bitmap) { Player player = this.player; boolean ongoing = getOngoing(player); - Notification notification = createNotification(player, ongoing, bitmap); + Notification notification = createNotification(player, builder, ongoing, bitmap); if (notification == null) { stopNotification(/* dismissedByUser= */ false); return null; @@ -923,6 +926,11 @@ public class PlayerNotificationManager { * Creates the notification given the current player state. * * @param player The player for which state to build a notification. + * @param builder A builder that can optionally be used for creating the notification. The same + * builder is passed each time this method is called, since reusing the same builder can + * prevent notification flicker when {@code Util#SDK_INT} < 21. This means implementations + * must take care to ensure anything set on the builder during a previous call is cleared, if + * no longer required. * @param ongoing Whether the notification should be ongoing. * @param largeIcon The large icon to be used. * @return The {@link Notification} which has been built, or {@code null} if no notification @@ -930,11 +938,15 @@ public class PlayerNotificationManager { */ @Nullable protected Notification createNotification( - Player player, boolean ongoing, @Nullable Bitmap largeIcon) { + Player player, + NotificationCompat.Builder builder, + boolean ongoing, + @Nullable Bitmap largeIcon) { if (player.getPlaybackState() == Player.STATE_IDLE) { return null; } - NotificationCompat.Builder builder = new NotificationCompat.Builder(context, channelId); + + builder.mActions.clear(); List actionNames = getActions(player); for (int i = 0; i < actionNames.size(); i++) { String actionName = actionNames.get(i); @@ -946,7 +958,7 @@ public class PlayerNotificationManager { builder.addAction(action); } } - // Create a media style notification. + MediaStyle mediaStyle = new MediaStyle(); if (mediaSessionToken != null) { mediaStyle.setMediaSession(mediaSessionToken); @@ -955,9 +967,11 @@ public class PlayerNotificationManager { // Configure dismiss action prior to API 21 ('x' button). mediaStyle.setShowCancelButton(!ongoing); mediaStyle.setCancelButtonIntent(dismissPendingIntent); + builder.setStyle(mediaStyle); + // Set intent which is sent if the user selects 'clear all' builder.setDeleteIntent(dismissPendingIntent); - builder.setStyle(mediaStyle); + // Set notification properties from getters. builder .setBadgeIconType(badgeIconType) @@ -968,7 +982,10 @@ public class PlayerNotificationManager { .setVisibility(visibility) .setPriority(priority) .setDefaults(defaults); - if (useChronometer + + // Changing "showWhen" causes notification flicker if SDK_INT < 21. + if (Util.SDK_INT >= 21 + && useChronometer && !player.isPlayingAd() && !player.isCurrentWindowDynamic() && player.getPlayWhenReady() @@ -980,6 +997,7 @@ public class PlayerNotificationManager { } else { builder.setShowWhen(false).setUsesChronometer(false); } + // Set media specific notification properties from MediaDescriptionAdapter. builder.setContentTitle(mediaDescriptionAdapter.getCurrentContentTitle(player)); builder.setContentText(mediaDescriptionAdapter.getCurrentContentText(player)); @@ -989,13 +1007,9 @@ public class PlayerNotificationManager { mediaDescriptionAdapter.getCurrentLargeIcon( player, new BitmapCallback(++currentNotificationTag)); } - if (largeIcon != null) { - builder.setLargeIcon(largeIcon); - } - PendingIntent contentIntent = mediaDescriptionAdapter.createCurrentContentIntent(player); - if (contentIntent != null) { - builder.setContentIntent(contentIntent); - } + setLargeIcon(builder, largeIcon); + builder.setContentIntent(mediaDescriptionAdapter.createCurrentContentIntent(player)); + return builder.build(); } @@ -1086,54 +1100,6 @@ public class PlayerNotificationManager { && player.getPlayWhenReady(); } - private static Map createPlaybackActions( - Context context, int instanceId) { - Map actions = new HashMap<>(); - actions.put( - ACTION_PLAY, - new NotificationCompat.Action( - R.drawable.exo_notification_play, - context.getString(R.string.exo_controls_play_description), - createBroadcastIntent(ACTION_PLAY, context, instanceId))); - actions.put( - ACTION_PAUSE, - new NotificationCompat.Action( - R.drawable.exo_notification_pause, - context.getString(R.string.exo_controls_pause_description), - createBroadcastIntent(ACTION_PAUSE, context, instanceId))); - actions.put( - ACTION_STOP, - new NotificationCompat.Action( - R.drawable.exo_notification_stop, - context.getString(R.string.exo_controls_stop_description), - createBroadcastIntent(ACTION_STOP, context, instanceId))); - actions.put( - ACTION_REWIND, - new NotificationCompat.Action( - R.drawable.exo_notification_rewind, - context.getString(R.string.exo_controls_rewind_description), - createBroadcastIntent(ACTION_REWIND, context, instanceId))); - actions.put( - ACTION_FAST_FORWARD, - new NotificationCompat.Action( - R.drawable.exo_notification_fastforward, - context.getString(R.string.exo_controls_fastforward_description), - createBroadcastIntent(ACTION_FAST_FORWARD, context, instanceId))); - actions.put( - ACTION_PREVIOUS, - new NotificationCompat.Action( - R.drawable.exo_notification_previous, - context.getString(R.string.exo_controls_previous_description), - createBroadcastIntent(ACTION_PREVIOUS, context, instanceId))); - actions.put( - ACTION_NEXT, - new NotificationCompat.Action( - R.drawable.exo_notification_next, - context.getString(R.string.exo_controls_next_description), - createBroadcastIntent(ACTION_NEXT, context, instanceId))); - return actions; - } - private void previous(Player player) { Timeline timeline = player.getCurrentTimeline(); if (timeline.isEmpty() || player.isPlayingAd()) { @@ -1196,6 +1162,54 @@ public class PlayerNotificationManager { && player.getPlayWhenReady(); } + private static Map createPlaybackActions( + Context context, int instanceId) { + Map actions = new HashMap<>(); + actions.put( + ACTION_PLAY, + new NotificationCompat.Action( + R.drawable.exo_notification_play, + context.getString(R.string.exo_controls_play_description), + createBroadcastIntent(ACTION_PLAY, context, instanceId))); + actions.put( + ACTION_PAUSE, + new NotificationCompat.Action( + R.drawable.exo_notification_pause, + context.getString(R.string.exo_controls_pause_description), + createBroadcastIntent(ACTION_PAUSE, context, instanceId))); + actions.put( + ACTION_STOP, + new NotificationCompat.Action( + R.drawable.exo_notification_stop, + context.getString(R.string.exo_controls_stop_description), + createBroadcastIntent(ACTION_STOP, context, instanceId))); + actions.put( + ACTION_REWIND, + new NotificationCompat.Action( + R.drawable.exo_notification_rewind, + context.getString(R.string.exo_controls_rewind_description), + createBroadcastIntent(ACTION_REWIND, context, instanceId))); + actions.put( + ACTION_FAST_FORWARD, + new NotificationCompat.Action( + R.drawable.exo_notification_fastforward, + context.getString(R.string.exo_controls_fastforward_description), + createBroadcastIntent(ACTION_FAST_FORWARD, context, instanceId))); + actions.put( + ACTION_PREVIOUS, + new NotificationCompat.Action( + R.drawable.exo_notification_previous, + context.getString(R.string.exo_controls_previous_description), + createBroadcastIntent(ACTION_PREVIOUS, context, instanceId))); + actions.put( + ACTION_NEXT, + new NotificationCompat.Action( + R.drawable.exo_notification_next, + context.getString(R.string.exo_controls_next_description), + createBroadcastIntent(ACTION_NEXT, context, instanceId))); + return actions; + } + private static PendingIntent createBroadcastIntent( String action, Context context, int instanceId) { Intent intent = new Intent(action).setPackage(context.getPackageName()); @@ -1204,6 +1218,11 @@ public class PlayerNotificationManager { context, instanceId, intent, PendingIntent.FLAG_CANCEL_CURRENT); } + @SuppressWarnings("nullness:argument.type.incompatible") + private static void setLargeIcon(NotificationCompat.Builder builder, @Nullable Bitmap largeIcon) { + builder.setLargeIcon(largeIcon); + } + private class PlayerListener implements Player.EventListener { @Override