From 3dec4266a361d343b9fcd0e7ef0f7989ec690d93 Mon Sep 17 00:00:00 2001 From: christosts Date: Thu, 5 May 2022 12:57:47 +0100 Subject: [PATCH] Remove unused classes from session module Remove PlayerNotificationManager and DefaultMediaDescriptionAdapter which are unused in session. The functionality is covered by the MediaNotification.Provider. PiperOrigin-RevId: 446687875 --- .../DefaultMediaDescriptionAdapter.java | 73 -- .../session/PlayerNotificationManager.java | 1148 ----------------- 2 files changed, 1221 deletions(-) delete mode 100644 libraries/session/src/main/java/androidx/media3/session/DefaultMediaDescriptionAdapter.java delete mode 100644 libraries/session/src/main/java/androidx/media3/session/PlayerNotificationManager.java diff --git a/libraries/session/src/main/java/androidx/media3/session/DefaultMediaDescriptionAdapter.java b/libraries/session/src/main/java/androidx/media3/session/DefaultMediaDescriptionAdapter.java deleted file mode 100644 index e70c4185ec..0000000000 --- a/libraries/session/src/main/java/androidx/media3/session/DefaultMediaDescriptionAdapter.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2021 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 androidx.media3.session; - -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.text.TextUtils; -import androidx.annotation.Nullable; -import androidx.media3.common.Player; -import androidx.media3.common.util.UnstableApi; -import androidx.media3.session.PlayerNotificationManager.BitmapCallback; -import androidx.media3.session.PlayerNotificationManager.MediaDescriptionAdapter; - -/** - * Default implementation of {@link MediaDescriptionAdapter}. - * - *

Uses values from the {@link Player#getMediaMetadata() player mediaMetadata} to populate the - * notification. - */ -@UnstableApi -public final class DefaultMediaDescriptionAdapter implements MediaDescriptionAdapter { - - /** Creates a default {@link MediaDescriptionAdapter}. */ - public DefaultMediaDescriptionAdapter() {} - - @Override - public CharSequence getCurrentContentTitle(MediaSession session) { - Player player = session.getPlayer(); - @Nullable CharSequence displayTitle = player.getMediaMetadata().displayTitle; - if (!TextUtils.isEmpty(displayTitle)) { - return displayTitle; - } - - @Nullable CharSequence title = player.getMediaMetadata().title; - return title != null ? title : ""; - } - - @Nullable - @Override - public CharSequence getCurrentContentText(MediaSession session) { - Player player = session.getPlayer(); - @Nullable CharSequence artist = player.getMediaMetadata().artist; - if (!TextUtils.isEmpty(artist)) { - return artist; - } - - return player.getMediaMetadata().albumArtist; - } - - @Nullable - @Override - public Bitmap getCurrentLargeIcon(MediaSession session, BitmapCallback callback) { - Player player = session.getPlayer(); - @Nullable byte[] data = player.getMediaMetadata().artworkData; - if (data == null) { - return null; - } - return BitmapFactory.decodeByteArray(data, /* offset= */ 0, data.length); - } -} diff --git a/libraries/session/src/main/java/androidx/media3/session/PlayerNotificationManager.java b/libraries/session/src/main/java/androidx/media3/session/PlayerNotificationManager.java deleted file mode 100644 index 7796aa2624..0000000000 --- a/libraries/session/src/main/java/androidx/media3/session/PlayerNotificationManager.java +++ /dev/null @@ -1,1148 +0,0 @@ -/* - * Copyright 2021 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 androidx.media3.session; - -import static androidx.media3.common.Player.COMMAND_INVALID; -import static androidx.media3.common.Player.COMMAND_PLAY_PAUSE; -import static androidx.media3.common.Player.COMMAND_SEEK_BACK; -import static androidx.media3.common.Player.COMMAND_SEEK_FORWARD; -import static androidx.media3.common.Player.COMMAND_SEEK_TO_NEXT; -import static androidx.media3.common.Player.COMMAND_SEEK_TO_PREVIOUS; -import static androidx.media3.common.Player.EVENT_IS_PLAYING_CHANGED; -import static androidx.media3.common.Player.EVENT_MEDIA_METADATA_CHANGED; -import static androidx.media3.common.Player.EVENT_PLAYBACK_PARAMETERS_CHANGED; -import static androidx.media3.common.Player.EVENT_PLAYBACK_STATE_CHANGED; -import static androidx.media3.common.Player.EVENT_PLAY_WHEN_READY_CHANGED; -import static androidx.media3.common.Player.EVENT_POSITION_DISCONTINUITY; -import static androidx.media3.common.Player.EVENT_REPEAT_MODE_CHANGED; -import static androidx.media3.common.Player.EVENT_SHUFFLE_MODE_ENABLED_CHANGED; -import static androidx.media3.common.Player.EVENT_TIMELINE_CHANGED; -import static androidx.media3.common.util.Assertions.checkArgument; -import static androidx.media3.common.util.Assertions.checkStateNotNull; -import static java.lang.annotation.ElementType.TYPE_USE; - -import android.app.Notification; -import android.app.NotificationChannel; -import android.app.PendingIntent; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.graphics.Bitmap; -import android.graphics.Color; -import android.net.Uri; -import android.os.Bundle; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import androidx.annotation.DrawableRes; -import androidx.annotation.IntDef; -import androidx.annotation.IntRange; -import androidx.annotation.Nullable; -import androidx.core.app.NotificationCompat; -import androidx.core.app.NotificationManagerCompat; -import androidx.core.content.ContextCompat; -import androidx.media3.common.C; -import androidx.media3.common.Player; -import androidx.media3.common.util.Assertions; -import androidx.media3.common.util.BundleableUtil; -import androidx.media3.common.util.Log; -import androidx.media3.common.util.NotificationUtil; -import androidx.media3.common.util.UnstableApi; -import androidx.media3.common.util.Util; -import androidx.media3.session.MediaStyleNotificationHelper.MediaStyle; -import com.google.common.util.concurrent.ListenableFuture; -import java.lang.annotation.Documented; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import org.checkerframework.checker.initialization.qual.Initialized; - -/** - * Starts, updates and cancels a media style notification for a {@link MediaSession}. The actions - * included in the notification can be customized along with their drawables, as described below. - * - *

When notification is no longer required, call {@link #release()} to release resources. - * - *

Overriding drawables

- * - * The drawables used by PlayerNotificationManager can be overridden by drawables with the same - * names defined in your application. The drawables that can be overridden are: - * - * - * - *

Alternatively, the action icons can be set programatically by using the {@link Builder}. - * - *

Unlike the drawables above, the large icon (i.e. the icon passed to {@link - * NotificationCompat.Builder#setLargeIcon(Bitmap)} cannot be overridden in this way. Instead, the - * large icon is obtained from the {@link MediaDescriptionAdapter} passed to {@link - * Builder#setMediaDescriptionAdapter(MediaDescriptionAdapter)}. - */ -@UnstableApi -public class PlayerNotificationManager { - - /** An adapter to provide content assets of the media currently playing. */ - public interface MediaDescriptionAdapter { - - /** - * Gets the content title for the current media item. - * - *

See {@link NotificationCompat.Builder#setContentTitle(CharSequence)}. - * - * @param session The {@link MediaSession} for which a notification is being built. - * @return The content title for the current media item. - */ - CharSequence getCurrentContentTitle(MediaSession session); - - /** - * Gets the content text for the current media item. - * - *

See {@link NotificationCompat.Builder#setContentText(CharSequence)}. - * - * @param session The {@link MediaSession} for which a notification is being built. - * @return The content text for the current media item, or null if no context text should be - * displayed. - */ - @Nullable - CharSequence getCurrentContentText(MediaSession session); - - /** - * Gets the content sub text for the current media item. - * - *

See {@link NotificationCompat.Builder#setSubText(CharSequence)}. - * - * @param session The {@link MediaSession} for which a notification is being built. - * @return The content subtext for the current media item, or null if no subtext should be - * displayed. - */ - @Nullable - default CharSequence getCurrentSubText(MediaSession session) { - return null; - } - - /** - * Gets the large icon for the current media item. - * - *

When a bitmap needs to be loaded asynchronously, a placeholder bitmap (or null) should be - * returned. The actual bitmap should be passed to the {@link BitmapCallback} once it has been - * loaded. Because the adapter may be called multiple times for the same media item, bitmaps - * should be cached by the app and returned synchronously when possible. - * - *

See {@link NotificationCompat.Builder#setLargeIcon(Bitmap)}. - * - * @param session The {@link MediaSession} for which a notification is being built. - * @param callback A {@link BitmapCallback} to provide a {@link Bitmap} asynchronously. - * @return The large icon for the current media item, or null if the icon will be returned - * through the {@link BitmapCallback} or if no icon should be displayed. - */ - @Nullable - Bitmap getCurrentLargeIcon(MediaSession session, BitmapCallback callback); - } - - /** A listener for changes to the notification. */ - public interface NotificationListener { - - /** - * Called after the notification has been cancelled. - * - * @param notificationId The id of the notification which has been cancelled. - * @param dismissedByUser {@code true} if the notification is cancelled because the user - * dismissed the notification. - */ - default void onNotificationCancelled(int notificationId, boolean dismissedByUser) {} - - /** - * Called each time after the notification has been posted. - * - *

For a service, the {@code ongoing} flag can be used as an indicator as to whether it - * should be in the foreground. - * - * @param notificationId The id of the notification which has been posted. - * @param notification The {@link Notification}. - * @param ongoing Whether the notification is ongoing. - */ - default void onNotificationPosted( - int notificationId, Notification notification, boolean ongoing) {} - } - - /** A builder for {@link PlayerNotificationManager} instances. */ - public static class Builder { - - protected final Context context; - protected final int notificationId; - protected final String channelId; - - protected MediaSession session; - protected Bundle connectionHints; - @Nullable protected NotificationListener notificationListener; - protected MediaDescriptionAdapter mediaDescriptionAdapter; - protected int channelNameResourceId; - protected int channelDescriptionResourceId; - protected int channelImportance; - protected int smallIconResourceId; - @Nullable protected String groupKey; - - /** - * Creates an instance. - * - * @param context The {@link Context}. - * @param notificationId The id of the notification to be posted. Must be greater than 0. - * @param session The session to build notification with. - * @param channelId The id of the notification channel. - */ - public Builder( - Context context, - MediaSession session, - @IntRange(from = 1) int notificationId, - String channelId) { - checkArgument(notificationId > 0); - this.context = context; - this.session = session; - this.notificationId = notificationId; - this.channelId = channelId; - connectionHints = Bundle.EMPTY; - channelImportance = NotificationUtil.IMPORTANCE_LOW; - mediaDescriptionAdapter = new DefaultMediaDescriptionAdapter(); - smallIconResourceId = R.drawable.media3_notification_small_icon; - } - - /** - * The connection hints for identify {@link MediaController} to deliver commands from the - * notification. - * - * @return This builder - */ - public Builder setConnectionHints(Bundle connectionHints) { - this.connectionHints = Assertions.checkNotNull(connectionHints); - return this; - } - - /** - * The name of the channel. If set to a value other than {@code 0}, the channel is automatically - * created when {@link #build()} is called. If the application has already created the - * notification channel, then this method should not be called. - * - *

The default is {@code 0}. - * - * @return This builder. - */ - public Builder setChannelNameResourceId(int channelNameResourceId) { - this.channelNameResourceId = channelNameResourceId; - return this; - } - - /** - * The description of the channel. Ignored if {@link #setChannelNameResourceId(int)} is not - * called with a value other than {@code 0}. If the application has already created the - * notification channel, then this method should not be called. - * - *

The default is {@code 0}. - * - * @return This builder. - */ - public Builder setChannelDescriptionResourceId(int channelDescriptionResourceId) { - this.channelDescriptionResourceId = channelDescriptionResourceId; - return this; - } - - /** - * The importance of the channel. Ignored if {@link #setChannelNameResourceId(int)} is not - * called with a value other than {@code 0}. If the application has already created the - * notification channel, then this method should not be called. - * - *

The default is {@link NotificationUtil#IMPORTANCE_LOW}. - * - * @return This builder. - */ - public Builder setChannelImportance(@NotificationUtil.Importance int channelImportance) { - this.channelImportance = channelImportance; - return this; - } - - /** - * The {@link NotificationListener} to be used. - * - *

The default is {@code null}. - * - * @return This builder. - */ - public Builder setNotificationListener(NotificationListener notificationListener) { - this.notificationListener = notificationListener; - return this; - } - - /** - * The resource id of the small icon of the notification shown in the status bar. See {@link - * NotificationCompat.Builder#setSmallIcon(int)}. - * - *

The default is {@code R.drawable#media3_notification_small_icon}. - * - * @return This builder. - */ - public Builder setSmallIconResourceId(int smallIconResourceId) { - this.smallIconResourceId = smallIconResourceId; - return this; - } - - /** - * The key of the group the media notification should belong to. - * - *

The default is {@code null} - * - * @return This builder. - */ - public Builder setGroup(String groupKey) { - this.groupKey = groupKey; - return this; - } - - /** - * The {@link MediaDescriptionAdapter} to be queried for the notification contents. - * - *

The default is {@link DefaultMediaDescriptionAdapter} with no {@link PendingIntent} - * - * @return This builder. - */ - public Builder setMediaDescriptionAdapter(MediaDescriptionAdapter mediaDescriptionAdapter) { - this.mediaDescriptionAdapter = mediaDescriptionAdapter; - return this; - } - - /** Builds the {@link PlayerNotificationManager}. */ - public PlayerNotificationManager build() { - if (channelNameResourceId != 0) { - NotificationUtil.createNotificationChannel( - context, - channelId, - channelNameResourceId, - channelDescriptionResourceId, - channelImportance); - } - - return new PlayerNotificationManager( - context, - session, - connectionHints, - channelId, - notificationId, - mediaDescriptionAdapter, - notificationListener, - smallIconResourceId, - groupKey); - } - } - - /** Receives a {@link Bitmap}. */ - public final class BitmapCallback { - private final int notificationTag; - - /** Create the receiver. */ - private BitmapCallback(int notificationTag) { - this.notificationTag = notificationTag; - } - - /** - * Called when {@link Bitmap} is available. - * - * @param bitmap The bitmap to use as the large icon of the notification. - */ - public void onBitmap(final Bitmap bitmap) { - if (bitmap != null) { - postUpdateNotificationBitmap(bitmap, notificationTag); - } - } - } - - /** The action which is executed when a button in the notification is clicked. */ - private static final String INTENT_ACTION_COMMAND = "androidx.media3.session.command"; - - /** - * The action which is executed when the notification is dismissed. It cancels the notification - * and calls {@link NotificationListener#onNotificationCancelled(int, boolean)}. - */ - private static final String INTENT_ACTION_DISMISS = - "androidx.media3.session.notification.dismiss"; - - private static final String INTENT_EXTRA_PLAYER_COMMAND = - "androidx.media3.session.EXTRA_PLAYER_COMMAND"; - private static final String INTENT_EXTRA_SESSION_COMMAND = - "androidx.media3.session.EXTRA_SESSION_COMMAND"; - private static final String INTENT_EXTRA_INSTANCE_ID = - "androidx.media3.session.notificaiton.EXTRA_INSTANCE_ID"; - private static final String INTENT_SCHEME = "media3"; - - private static final String TAG = "NotificationManager"; - - // Internal messages. - - private static final int MSG_START_OR_UPDATE_NOTIFICATION = 1; - private static final int MSG_UPDATE_NOTIFICATION_BITMAP = 2; - - /** - * Visibility of notification on the lock screen. One of {@link - * NotificationCompat#VISIBILITY_PRIVATE}, {@link NotificationCompat#VISIBILITY_PUBLIC} or {@link - * NotificationCompat#VISIBILITY_SECRET}. - */ - @Documented - @Retention(RetentionPolicy.SOURCE) - @Target(TYPE_USE) - @IntDef({ - NotificationCompat.VISIBILITY_PRIVATE, - NotificationCompat.VISIBILITY_PUBLIC, - NotificationCompat.VISIBILITY_SECRET - }) - public @interface Visibility {} - - /** - * Priority of the notification (required for API 25 and lower). One of {@link - * NotificationCompat#PRIORITY_DEFAULT}, {@link NotificationCompat#PRIORITY_MAX}, {@link - * NotificationCompat#PRIORITY_HIGH}, {@link NotificationCompat#PRIORITY_LOW }or {@link - * NotificationCompat#PRIORITY_MIN}. - */ - @Documented - @Retention(RetentionPolicy.SOURCE) - @Target(TYPE_USE) - @IntDef({ - NotificationCompat.PRIORITY_DEFAULT, - NotificationCompat.PRIORITY_MAX, - NotificationCompat.PRIORITY_HIGH, - NotificationCompat.PRIORITY_LOW, - NotificationCompat.PRIORITY_MIN - }) - public @interface Priority {} - - private static int instanceIdCounter; - - private final Context context; - private final MediaSession session; - private final ListenableFuture controllerFuture; - private final String channelId; - private final int notificationId; - private final MediaDescriptionAdapter mediaDescriptionAdapter; - @Nullable private final NotificationListener notificationListener; - private final Handler mainHandler; - private final NotificationManagerCompat notificationManager; - private final IntentFilter intentFilter; - private final NotificationBroadcastReceiver notificationBroadcastReceiver; - private final PendingIntent dismissPendingIntent; - private final int instanceId; - private final CommandButton playButton; - private final CommandButton pauseButton; - private final CommandButton seekToPreviousButton; - private final CommandButton seekToNextButton; - private final CommandButton seekBackButton; - private final CommandButton seekForwardButton; - - @Nullable private NotificationCompat.Builder builder; - private boolean isNotificationStarted; - private int currentNotificationTag; - private int badgeIconType; - private boolean colorized; - private int defaults; - private int color; - @DrawableRes private int smallIconResourceId; - private int visibility; - @Priority private int priority; - private boolean useChronometer; - @Nullable private String groupKey; - - protected PlayerNotificationManager( - Context context, - MediaSession session, - Bundle connectionHints, - String channelId, - int notificationId, - MediaDescriptionAdapter mediaDescriptionAdapter, - @Nullable NotificationListener notificationListener, - int smallIconResourceId, - @Nullable String groupKey) { - context = context.getApplicationContext(); - this.context = context; - this.session = session; - this.channelId = channelId; - this.notificationId = notificationId; - this.mediaDescriptionAdapter = mediaDescriptionAdapter; - this.notificationListener = notificationListener; - this.smallIconResourceId = smallIconResourceId; - this.groupKey = groupKey; - instanceId = instanceIdCounter++; - // This fails the nullness checker because handleMessage() is 'called' while `this` is still - // @UnderInitialization. No tasks are scheduled on mainHandler before the constructor completes, - // so this is safe and we can suppress the warning. - @SuppressWarnings("nullness:methodref.receiver.bound") - Handler mainHandler = Util.createHandler(Looper.getMainLooper(), this::handleMessage); - this.mainHandler = mainHandler; - controllerFuture = - new MediaController.Builder(context, session.getToken()) - .setApplicationLooper(Looper.getMainLooper()) - .setConnectionHints(connectionHints) - .setListener(new MediaControllerListener()) - .buildAsync(); - controllerFuture.addListener( - () -> { - @SuppressWarnings("nullness:assignment") - @Initialized - PlayerNotificationManager thisRef = this; - MediaController controller = thisRef.getMediaControllerOrNull(); - if (controller != null) { - controller.addListener(new PlayerListener()); - } - }, - ContextCompat.getMainExecutor(context)); - notificationManager = NotificationManagerCompat.from(context); - notificationBroadcastReceiver = new NotificationBroadcastReceiver(); - intentFilter = new IntentFilter(); - colorized = true; - useChronometer = true; - color = Color.TRANSPARENT; - defaults = 0; - priority = NotificationCompat.PRIORITY_LOW; - badgeIconType = NotificationCompat.BADGE_ICON_SMALL; - visibility = NotificationCompat.VISIBILITY_PUBLIC; - - // initialize default buttons - playButton = - new CommandButton.Builder() - .setDisplayName(context.getText(R.string.media3_controls_play_description)) - .setIconResId(R.drawable.media3_notification_play) - .setPlayerCommand(COMMAND_PLAY_PAUSE) - .build(); - pauseButton = - new CommandButton.Builder() - .setDisplayName(context.getText(R.string.media3_controls_pause_description)) - .setIconResId(R.drawable.media3_notification_pause) - .setPlayerCommand(COMMAND_PLAY_PAUSE) - .build(); - seekToPreviousButton = - new CommandButton.Builder() - .setDisplayName(context.getText(R.string.media3_controls_seek_to_previous_description)) - .setIconResId(R.drawable.media3_notification_seek_to_previous) - .setPlayerCommand(COMMAND_SEEK_TO_PREVIOUS) - .build(); - seekToNextButton = - new CommandButton.Builder() - .setDisplayName(context.getText(R.string.media3_controls_seek_to_next_description)) - .setIconResId(R.drawable.media3_notification_seek_to_next) - .setPlayerCommand(COMMAND_SEEK_TO_NEXT) - .build(); - seekBackButton = - new CommandButton.Builder() - .setDisplayName(context.getText(R.string.media3_controls_seek_back_description)) - .setIconResId(R.drawable.media3_notification_seek_back) - .setPlayerCommand(COMMAND_SEEK_BACK) - .build(); - seekForwardButton = - new CommandButton.Builder() - .setDisplayName(context.getText(R.string.media3_controls_seek_forward_description)) - .setIconResId(R.drawable.media3_notification_seek_forward) - .setPlayerCommand(COMMAND_SEEK_FORWARD) - .build(); - intentFilter.addAction(INTENT_ACTION_COMMAND); - intentFilter.addAction(INTENT_ACTION_DISMISS); - intentFilter.addDataScheme(INTENT_SCHEME); - dismissPendingIntent = createBroadcastIntent(context, INTENT_ACTION_DISMISS, instanceId); - } - - /* Releases all resources, such as internal {@link MediaController}. */ - public void release() { - // This will indirectly call stopNotification(/* dismissedByUser= */ false). - MediaController.releaseFuture(controllerFuture); - } - - /** - * Sets the badge icon type of the notification. - * - *

See {@link NotificationCompat.Builder#setBadgeIconType(int)}. - * - * @param badgeIconType The badge icon type. - */ - public final void setBadgeIconType(@NotificationCompat.BadgeIconType int badgeIconType) { - if (this.badgeIconType == badgeIconType) { - return; - } - switch (badgeIconType) { - case NotificationCompat.BADGE_ICON_NONE: - case NotificationCompat.BADGE_ICON_SMALL: - case NotificationCompat.BADGE_ICON_LARGE: - this.badgeIconType = badgeIconType; - break; - default: - throw new IllegalArgumentException(); - } - invalidate(); - } - - /** - * Sets whether the notification should be colorized. When set, the color set with {@link - * #setColor(int)} will be used as the background color for the notification. - * - *

See {@link NotificationCompat.Builder#setColorized(boolean)}. - * - * @param colorized Whether to colorize the notification. - */ - public final void setColorized(boolean colorized) { - if (this.colorized != colorized) { - this.colorized = colorized; - invalidate(); - } - } - - /** - * Sets the defaults. - * - *

See {@link NotificationCompat.Builder#setDefaults(int)}. - * - * @param defaults The default notification options. - */ - public final void setDefaults(int defaults) { - if (this.defaults != defaults) { - this.defaults = defaults; - invalidate(); - } - } - - /** - * Sets the accent color of the notification. - * - *

See {@link NotificationCompat.Builder#setColor(int)}. - * - * @param color The color, in ARGB integer form like the constants in {@link Color}. - */ - public final void setColor(int color) { - if (this.color != color) { - this.color = color; - invalidate(); - } - } - - /** - * Sets the priority of the notification required for API 25 and lower. - * - *

See {@link NotificationCompat.Builder#setPriority(int)}. - * - *

To set the priority for API levels above 25, you can create your own {@link - * NotificationChannel} with a given importance level and pass the id of the channel to {@link - * Builder#Builder(Context, MediaSession, int, String)}. - * - * @param priority The priority which can be one of {@link NotificationCompat#PRIORITY_DEFAULT}, - * {@link NotificationCompat#PRIORITY_MAX}, {@link NotificationCompat#PRIORITY_HIGH}, {@link - * NotificationCompat#PRIORITY_LOW} or {@link NotificationCompat#PRIORITY_MIN}. If not set - * {@link NotificationCompat#PRIORITY_LOW} is used by default. - */ - public final void setPriority(@Priority int priority) { - if (this.priority == priority) { - return; - } - switch (priority) { - case NotificationCompat.PRIORITY_DEFAULT: - case NotificationCompat.PRIORITY_MAX: - case NotificationCompat.PRIORITY_HIGH: - case NotificationCompat.PRIORITY_LOW: - case NotificationCompat.PRIORITY_MIN: - this.priority = priority; - break; - default: - throw new IllegalArgumentException(); - } - invalidate(); - } - - /** - * Sets the small icon of the notification which is also shown in the system status bar. - * - *

See {@link NotificationCompat.Builder#setSmallIcon(int)}. - * - * @param smallIconResourceId The resource id of the small icon. - */ - public final void setSmallIcon(@DrawableRes int smallIconResourceId) { - if (this.smallIconResourceId != smallIconResourceId) { - this.smallIconResourceId = smallIconResourceId; - invalidate(); - } - } - - /** - * Sets whether the elapsed time of the media playback should be displayed. - * - *

Note that this setting only works if all of the following are true: - * - *

- * - *

See {@link NotificationCompat.Builder#setUsesChronometer(boolean)}. - * - * @param useChronometer Whether to use chronometer. - */ - public final void setUseChronometer(boolean useChronometer) { - if (this.useChronometer != useChronometer) { - this.useChronometer = useChronometer; - invalidate(); - } - } - - /** - * Sets the visibility of the notification which determines whether and how the notification is - * shown when the device is in lock screen mode. - * - *

See {@link NotificationCompat.Builder#setVisibility(int)}. - * - * @param visibility The visibility which must be one of {@link - * NotificationCompat#VISIBILITY_PUBLIC}, {@link NotificationCompat#VISIBILITY_PRIVATE} or - * {@link NotificationCompat#VISIBILITY_SECRET}. - */ - public final void setVisibility(@Visibility int visibility) { - if (this.visibility == visibility) { - return; - } - switch (visibility) { - case NotificationCompat.VISIBILITY_PRIVATE: - case NotificationCompat.VISIBILITY_PUBLIC: - case NotificationCompat.VISIBILITY_SECRET: - this.visibility = visibility; - break; - default: - throw new IllegalStateException(); - } - invalidate(); - } - - /** Forces an update of the notification if already started. */ - public final void invalidate() { - if (isNotificationStarted) { - postStartOrUpdateNotification(); - } - } - - /** - * Gets the {@link MediaController} to send command to the session with. Can be {@code null} if - * the media controller isn't connected. - */ - @Nullable - public final MediaController getMediaControllerOrNull() { - try { - MediaController controller = controllerFuture.get(0, TimeUnit.MILLISECONDS); - return controller.isConnected() ? controller : null; - } catch (ExecutionException | InterruptedException | TimeoutException e) { - return null; - } - } - - private void startOrUpdateNotification(@Nullable Bitmap bitmap) { - boolean ongoing = getOngoing(); - builder = createNotification(builder, ongoing, bitmap); - if (builder == null) { - stopNotification(/* dismissedByUser= */ false); - return; - } - Notification notification = builder.build(); - notificationManager.notify(notificationId, notification); - if (!isNotificationStarted) { - context.registerReceiver(notificationBroadcastReceiver, intentFilter); - } - if (notificationListener != null) { - // Always pass true for ongoing with the first notification to tell a service to go into - // foreground even when paused. - notificationListener.onNotificationPosted( - notificationId, notification, ongoing || !isNotificationStarted); - } - isNotificationStarted = true; - } - - private void stopNotification(boolean dismissedByUser) { - if (isNotificationStarted) { - isNotificationStarted = false; - mainHandler.removeMessages(MSG_START_OR_UPDATE_NOTIFICATION); - notificationManager.cancel(notificationId); - context.unregisterReceiver(notificationBroadcastReceiver); - if (notificationListener != null) { - notificationListener.onNotificationCancelled(notificationId, dismissedByUser); - } - } - } - - /** - * Creates the notification given the current session state. - * - * @param builder The builder used to build the last notification, or {@code null}. Re-using the - * builder when possible can prevent notification flicker when {@code Util#SDK_INT} < 21. - * @param ongoing Whether the notification should be ongoing. - * @param largeIcon The large icon to be used. - * @return The {@link NotificationCompat.Builder} on which to call {@link - * NotificationCompat.Builder#build()} to obtain the notification, or {@code null} if no - * notification should be displayed. - */ - @Nullable - protected NotificationCompat.Builder createNotification( - @Nullable NotificationCompat.Builder builder, boolean ongoing, @Nullable Bitmap largeIcon) { - Player player = session.getPlayer(); - if (player.getPlaybackState() == Player.STATE_IDLE && player.getCurrentTimeline().isEmpty()) { - return null; - } - - if (builder == null) { - builder = new NotificationCompat.Builder(context, channelId); - } - List actionButtons = getActionButtons(); - for (int i = 0; i < actionButtons.size(); i++) { - CommandButton button = actionButtons.get(i); - NotificationCompat.Action action = - new NotificationCompat.Action( - button.iconResId, - button.displayName, - createBroadcastIntent(context, button, instanceId)); - builder.addAction(action); - } - - MediaStyle mediaStyle = new MediaStyle(session); - mediaStyle.setShowActionsInCompactView(getActionButtonIndicesForCompactView(actionButtons)); - // 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); - - // Set notification properties from getters. - builder - .setBadgeIconType(badgeIconType) - .setOngoing(ongoing) - .setColor(color) - .setColorized(colorized) - .setSmallIcon(smallIconResourceId) - .setVisibility(visibility) - .setPriority(priority) - .setDefaults(defaults); - - // Changing "showWhen" causes notification flicker if SDK_INT < 21. - if (Util.SDK_INT >= 21 - && useChronometer - && player.isPlaying() - && !player.isPlayingAd() - && !player.isCurrentWindowDynamic() - && player.getPlaybackParameters().speed == 1f) { - builder - .setWhen(System.currentTimeMillis() - player.getContentPosition()) - .setShowWhen(true) - .setUsesChronometer(true); - } else { - builder.setShowWhen(false).setUsesChronometer(false); - } - - // Set media specific notification properties from MediaDescriptionAdapter. - builder.setContentTitle(mediaDescriptionAdapter.getCurrentContentTitle(session)); - builder.setContentText(mediaDescriptionAdapter.getCurrentContentText(session)); - builder.setSubText(mediaDescriptionAdapter.getCurrentSubText(session)); - if (largeIcon == null) { - largeIcon = - mediaDescriptionAdapter.getCurrentLargeIcon( - session, new BitmapCallback(++currentNotificationTag)); - } - setLargeIcon(builder, largeIcon); - MediaController controller = getMediaControllerOrNull(); - if (controller != null) { - builder.setContentIntent(controller.getSessionActivity()); - } - - if (groupKey != null) { - builder.setGroup(groupKey); - } - - builder.setOnlyAlertOnce(true); - return builder; - } - - /** - * Gets the names and order of the actions to be included in the notification at the current - * player state. - * - *

The playback and custom actions are combined and placed in the following order if not - * omitted: - * - *

-   *   +------------------------------------------------+
-   *   | prev | << | play/pause | >> | next |
-   *   +------------------------------------------------+
-   * 
- * - *

This method can be safely overridden. - */ - protected List getActionButtons() { - Player player = session.getPlayer(); - boolean enablePrevious = player.isCommandAvailable(COMMAND_SEEK_TO_PREVIOUS); - boolean enableRewind = player.isCommandAvailable(COMMAND_SEEK_BACK); - boolean enableFastForward = player.isCommandAvailable(COMMAND_SEEK_FORWARD); - boolean enableNext = player.isCommandAvailable(COMMAND_SEEK_TO_NEXT); - - List buttons = new ArrayList<>(); - if (enablePrevious) { - buttons.add(seekToPreviousButton); - } - if (enableRewind) { - buttons.add(seekBackButton); - } - if (shouldShowPauseButton()) { - buttons.add(pauseButton); - } else { - buttons.add(playButton); - } - if (enableFastForward) { - buttons.add(seekForwardButton); - } - if (enableNext) { - buttons.add(seekToNextButton); - } - return buttons; - } - - /** - * Gets an array with the indices of the buttons to be shown in compact mode. - * - *

This method can be overridden. The indices must refer to the list of actions passed as the - * first parameter. - * - * @param actionButtons The buttons of the actions included in the notification. - */ - @SuppressWarnings("unused") - protected int[] getActionButtonIndicesForCompactView(List actionButtons) { - int previousIndex = C.INDEX_UNSET; - int nextIndex = C.INDEX_UNSET; - int playPauseIndex = C.INDEX_UNSET; - for (int i = 0; i < actionButtons.size(); i++) { - CommandButton button = actionButtons.get(i); - switch (button.playerCommand) { - case COMMAND_PLAY_PAUSE: - playPauseIndex = i; - break; - case COMMAND_SEEK_TO_PREVIOUS: - previousIndex = i; - break; - case COMMAND_SEEK_TO_NEXT: - nextIndex = i; - break; - default: - // Do nothing - } - } - int[] actionIndices = new int[3]; - int actionCounter = 0; - if (previousIndex != C.INDEX_UNSET) { - actionIndices[actionCounter++] = previousIndex; - } - if (playPauseIndex != C.INDEX_UNSET) { - actionIndices[actionCounter++] = playPauseIndex; - } - if (nextIndex != C.INDEX_UNSET) { - actionIndices[actionCounter++] = nextIndex; - } - return Arrays.copyOf(actionIndices, actionCounter); - } - - /** Returns whether the generated notification should be ongoing. */ - protected boolean getOngoing() { - Player player = session.getPlayer(); - int playbackState = player.getPlaybackState(); - return (playbackState == Player.STATE_BUFFERING || playbackState == Player.STATE_READY) - && player.getPlayWhenReady(); - } - - private boolean shouldShowPauseButton() { - Player player = session.getPlayer(); - return player.getPlaybackState() != Player.STATE_ENDED - && player.getPlaybackState() != Player.STATE_IDLE - && player.getPlayWhenReady(); - } - - private void postStartOrUpdateNotification() { - if (!mainHandler.hasMessages(MSG_START_OR_UPDATE_NOTIFICATION)) { - mainHandler.sendEmptyMessage(MSG_START_OR_UPDATE_NOTIFICATION); - } - } - - private void postUpdateNotificationBitmap(Bitmap bitmap, int notificationTag) { - mainHandler - .obtainMessage( - MSG_UPDATE_NOTIFICATION_BITMAP, notificationTag, C.INDEX_UNSET /* ignored */, bitmap) - .sendToTarget(); - } - - private boolean handleMessage(Message msg) { - switch (msg.what) { - case MSG_START_OR_UPDATE_NOTIFICATION: - startOrUpdateNotification(/* bitmap= */ null); - break; - case MSG_UPDATE_NOTIFICATION_BITMAP: - if (isNotificationStarted && currentNotificationTag == msg.arg1) { - startOrUpdateNotification((Bitmap) msg.obj); - } - break; - default: - return false; - } - return true; - } - - private static PendingIntent createBroadcastIntent( - Context context, CommandButton button, int instanceId) { - Intent intent = new Intent(INTENT_ACTION_COMMAND).setPackage(context.getPackageName()); - intent.putExtra(INTENT_EXTRA_INSTANCE_ID, instanceId); - intent.putExtra(INTENT_EXTRA_PLAYER_COMMAND, button.playerCommand); - intent.putExtra( - INTENT_EXTRA_SESSION_COMMAND, BundleableUtil.toNullableBundle(button.sessionCommand)); - // Make intent distinguishable by Intent#filterEquals() due to the PendingIntent requirement. - Uri intentUri = - new Uri.Builder() - .scheme(INTENT_SCHEME) - .appendPath(Integer.toString(instanceId)) - .appendPath(Integer.toString(button.playerCommand)) - .appendPath(button.sessionCommand == null ? "null" : button.sessionCommand.customAction) - .build(); - intent.setData(intentUri); - return PendingIntent.getBroadcast(context, instanceId, intent, getPendingIntentFlags()); - } - - private static PendingIntent createBroadcastIntent( - Context context, String action, int instanceId) { - Intent intent = new Intent(action).setPackage(context.getPackageName()); - intent.putExtra(INTENT_EXTRA_INSTANCE_ID, instanceId); - return PendingIntent.getBroadcast(context, instanceId, intent, getPendingIntentFlags()); - } - - @SuppressWarnings("nullness:argument") - private static void setLargeIcon(NotificationCompat.Builder builder, @Nullable Bitmap largeIcon) { - builder.setLargeIcon(largeIcon); - } - - private static int getPendingIntentFlags() { - return Util.SDK_INT >= 23 - ? PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE - : PendingIntent.FLAG_UPDATE_CURRENT; - } - - private class PlayerListener implements Player.Listener { - - @Override - public void onEvents(Player player, Player.Events events) { - if (events.containsAny( - EVENT_PLAYBACK_STATE_CHANGED, - EVENT_PLAY_WHEN_READY_CHANGED, - EVENT_IS_PLAYING_CHANGED, - EVENT_TIMELINE_CHANGED, - EVENT_PLAYBACK_PARAMETERS_CHANGED, - EVENT_POSITION_DISCONTINUITY, - EVENT_REPEAT_MODE_CHANGED, - EVENT_SHUFFLE_MODE_ENABLED_CHANGED, - EVENT_MEDIA_METADATA_CHANGED)) { - postStartOrUpdateNotification(); - } - } - } - - private class MediaControllerListener implements MediaController.Listener { - - @Override - public void onDisconnected(MediaController controller) { - stopNotification(/* dismissedByUser= */ false); - } - - @Override - public void onAvailableSessionCommandsChanged( - MediaController controller, SessionCommands commands) { - postStartOrUpdateNotification(); - } - } - - private class NotificationBroadcastReceiver extends BroadcastReceiver { - - @SuppressWarnings("deprecation") - @Override - public void onReceive(Context context, Intent intent) { - MediaController controller = getMediaControllerOrNull(); - if (controller == null - || !isNotificationStarted - || intent.getIntExtra(INTENT_EXTRA_INSTANCE_ID, instanceId) != instanceId) { - return; - } - String action = intent.getAction(); - if (INTENT_ACTION_COMMAND.equals(action)) { - @Player.Command - int playerCommand = intent.getIntExtra(INTENT_EXTRA_PLAYER_COMMAND, COMMAND_INVALID); - switch (playerCommand) { - case COMMAND_PLAY_PAUSE: - if (!controller.getPlayWhenReady()) { - if (controller.getPlaybackState() == controller.STATE_IDLE) { - controller.prepare(); - } else if (controller.getPlaybackState() == controller.STATE_ENDED) { - controller.seekToDefaultPosition(controller.getCurrentWindowIndex()); - } - controller.setPlayWhenReady(true); - } else { - controller.setPlayWhenReady(false); - } - break; - case COMMAND_SEEK_TO_PREVIOUS: - controller.seekToPrevious(); - break; - case COMMAND_SEEK_BACK: - controller.seekBack(); - break; - case COMMAND_SEEK_FORWARD: - controller.seekForward(); - break; - case COMMAND_SEEK_TO_NEXT: - controller.seekToNext(); - break; - case COMMAND_INVALID: - SessionCommand sessionCommand = - checkStateNotNull( - BundleableUtil.fromNullableBundle( - SessionCommand.CREATOR, - intent.getBundleExtra(INTENT_EXTRA_SESSION_COMMAND))); - ListenableFuture unused = - controller.sendCustomCommand(sessionCommand, /* args= */ Bundle.EMPTY); - break; - default: - Log.w(TAG, "Unsupported controller command, playerCommand=" + playerCommand); - break; - } - } else if (INTENT_ACTION_DISMISS.equals(action)) { - stopNotification(/* dismissedByUser= */ true); - } - } - } -}