Android 13 notification action buttons
This commit is contained in:
parent
6d694518fe
commit
04d53333e1
|
@ -29,7 +29,7 @@ public class MediaSessionPlayerUi extends PlayerUi
|
|||
private static final String TAG = "MediaSessUi";
|
||||
|
||||
private MediaSessionCompat mediaSession;
|
||||
private MediaSessionConnector sessionConnector;
|
||||
public MediaSessionConnector sessionConnector;
|
||||
|
||||
private final String ignoreHardwareMediaButtonsKey;
|
||||
private boolean shouldIgnoreHardwareMediaButtons = false;
|
||||
|
|
|
@ -5,6 +5,7 @@ import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_T
|
|||
import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.ResultReceiver;
|
||||
import android.support.v4.media.MediaDescriptionCompat;
|
||||
|
@ -46,7 +47,16 @@ public class PlayQueueNavigator implements MediaSessionConnector.QueueNavigator
|
|||
@Override
|
||||
public long getSupportedQueueNavigatorActions(
|
||||
@Nullable final com.google.android.exoplayer2.Player exoPlayer) {
|
||||
return ACTION_SKIP_TO_NEXT | ACTION_SKIP_TO_PREVIOUS | ACTION_SKIP_TO_QUEUE_ITEM;
|
||||
// As documented in
|
||||
// https://developer.android.com/about/versions/13/behavior-changes-13#playback-controls
|
||||
// starting with android 13, setting ACTION_SKIP_TO_PREVIOUS and ACTION_SKIP_TO_NEXT forces
|
||||
// buttons 2 and 3 to be the system provided "Previous" and "Next".
|
||||
// Thus, we pretend to not support those actions to have the ability to customize those
|
||||
// buttons
|
||||
return ACTION_SKIP_TO_QUEUE_ITEM |
|
||||
(Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU ?
|
||||
ACTION_SKIP_TO_NEXT | ACTION_SKIP_TO_PREVIOUS :
|
||||
0);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
package org.schabi.newpipe.player.notification;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.media.session.PlaybackStateCompat;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.DrawableRes;
|
||||
|
@ -40,6 +43,8 @@ import static org.schabi.newpipe.player.notification.NotificationConstants.ACTIO
|
|||
import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_REPEAT;
|
||||
import static org.schabi.newpipe.player.notification.NotificationConstants.ACTION_SHUFFLE;
|
||||
|
||||
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector;
|
||||
|
||||
/**
|
||||
* This is a utility class for player notifications.
|
||||
*/
|
||||
|
@ -220,56 +225,108 @@ public final class NotificationUtil {
|
|||
@SuppressLint("RestrictedApi")
|
||||
private void updateActions(final NotificationCompat.Builder builder) {
|
||||
builder.mActions.clear();
|
||||
final var customActionProviders =
|
||||
new java.util.ArrayList<MediaSessionConnector.CustomActionProvider>();
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
addAction(builder, notificationSlots[i]);
|
||||
addAction(builder, notificationSlots[i], customActionProviders);
|
||||
}
|
||||
// Starting with android 13, instead of Notification.Builder#addAction, we need to use
|
||||
// PlaybackState.CustomAction . The way to route that through exoplayer2 seems to be
|
||||
// sessionConnector.setCustomActionProviders .
|
||||
// Only do this in Android >= 13 , since I haven't been able to test if this breaks
|
||||
// anything for earlier versions.
|
||||
// https://developer.android.com/about/versions/13/behavior-changes-13#playback-controls
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
player.UIs()
|
||||
.get(MediaSessionPlayerUi.class)
|
||||
.ifPresent(mediaSessionPlayerUi -> {
|
||||
mediaSessionPlayerUi.sessionConnector.setCustomActionProviders(
|
||||
customActionProviders.toArray(
|
||||
new MediaSessionConnector.CustomActionProvider[0]));
|
||||
});
|
||||
}
|
||||
}
|
||||
// A simple CustomActionProvider that just broadcasts an intent for the given action
|
||||
// on the given context.
|
||||
private static class IntentBroadcastAction implements MediaSessionConnector.CustomActionProvider {
|
||||
private final String action;
|
||||
private final CharSequence name;
|
||||
private final int icon;
|
||||
private final Context context;
|
||||
IntentBroadcastAction(final String action, final CharSequence name,
|
||||
final int icon, final Context context) {
|
||||
this.action = action;
|
||||
this.name = name;
|
||||
this.icon = icon;
|
||||
this.context = context;
|
||||
}
|
||||
@Override
|
||||
public void onCustomAction(final com.google.android.exoplayer2.Player player,
|
||||
final String action, @Nullable final Bundle extras) {
|
||||
context.sendBroadcast(new Intent(this.action));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public PlaybackStateCompat.CustomAction
|
||||
getCustomAction(final com.google.android.exoplayer2.Player player) {
|
||||
return new PlaybackStateCompat.CustomAction.Builder(action, name, icon).build();
|
||||
}
|
||||
}
|
||||
private void addAction(final NotificationCompat.Builder builder,
|
||||
@NotificationConstants.Action final int slot) {
|
||||
final NotificationCompat.Action action = getAction(slot);
|
||||
@NotificationConstants.Action final int slot,
|
||||
final List<MediaSessionConnector.CustomActionProvider> actions) {
|
||||
String[] intentAction = new String[1];
|
||||
final NotificationCompat.Action action = getAction(slot, intentAction);
|
||||
|
||||
if (action != null) {
|
||||
builder.addAction(action);
|
||||
if (intentAction[0] != null && intentAction[0] != ACTION_PLAY_PAUSE) {
|
||||
actions.add(new IntentBroadcastAction(intentAction[0],
|
||||
action.getTitle().toString(),
|
||||
action.getIconCompat().getResId(), player.getContext()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private NotificationCompat.Action getAction(
|
||||
@NotificationConstants.Action final int selectedAction) {
|
||||
@NotificationConstants.Action final int selectedAction,
|
||||
String[] outIntentAction) {
|
||||
final int baseActionIcon = NotificationConstants.ACTION_ICONS[selectedAction];
|
||||
switch (selectedAction) {
|
||||
case NotificationConstants.PREVIOUS:
|
||||
return getAction(baseActionIcon,
|
||||
R.string.exo_controls_previous_description, ACTION_PLAY_PREVIOUS);
|
||||
R.string.exo_controls_previous_description, ACTION_PLAY_PREVIOUS, outIntentAction);
|
||||
|
||||
case NotificationConstants.NEXT:
|
||||
return getAction(baseActionIcon,
|
||||
R.string.exo_controls_next_description, ACTION_PLAY_NEXT);
|
||||
R.string.exo_controls_next_description, ACTION_PLAY_NEXT, outIntentAction);
|
||||
|
||||
case NotificationConstants.REWIND:
|
||||
return getAction(baseActionIcon,
|
||||
R.string.exo_controls_rewind_description, ACTION_FAST_REWIND);
|
||||
R.string.exo_controls_rewind_description, ACTION_FAST_REWIND, outIntentAction);
|
||||
|
||||
case NotificationConstants.FORWARD:
|
||||
return getAction(baseActionIcon,
|
||||
R.string.exo_controls_fastforward_description, ACTION_FAST_FORWARD);
|
||||
R.string.exo_controls_fastforward_description, ACTION_FAST_FORWARD, outIntentAction);
|
||||
|
||||
case NotificationConstants.SMART_REWIND_PREVIOUS:
|
||||
if (player.getPlayQueue() != null && player.getPlayQueue().size() > 1) {
|
||||
return getAction(R.drawable.exo_notification_previous,
|
||||
R.string.exo_controls_previous_description, ACTION_PLAY_PREVIOUS);
|
||||
R.string.exo_controls_previous_description, ACTION_PLAY_PREVIOUS, outIntentAction);
|
||||
} else {
|
||||
return getAction(R.drawable.exo_controls_rewind,
|
||||
R.string.exo_controls_rewind_description, ACTION_FAST_REWIND);
|
||||
R.string.exo_controls_rewind_description, ACTION_FAST_REWIND, outIntentAction);
|
||||
}
|
||||
|
||||
case NotificationConstants.SMART_FORWARD_NEXT:
|
||||
if (player.getPlayQueue() != null && player.getPlayQueue().size() > 1) {
|
||||
return getAction(R.drawable.exo_notification_next,
|
||||
R.string.exo_controls_next_description, ACTION_PLAY_NEXT);
|
||||
R.string.exo_controls_next_description, ACTION_PLAY_NEXT, outIntentAction);
|
||||
} else {
|
||||
return getAction(R.drawable.exo_controls_fastforward,
|
||||
R.string.exo_controls_fastforward_description, ACTION_FAST_FORWARD);
|
||||
R.string.exo_controls_fastforward_description, ACTION_FAST_FORWARD, outIntentAction);
|
||||
}
|
||||
|
||||
case NotificationConstants.PLAY_PAUSE_BUFFERING:
|
||||
|
@ -277,6 +334,7 @@ public final class NotificationUtil {
|
|||
|| player.getCurrentState() == Player.STATE_BLOCKED
|
||||
|| player.getCurrentState() == Player.STATE_BUFFERING) {
|
||||
// null intent -> show hourglass icon that does nothing when clicked
|
||||
outIntentAction[0] = null;
|
||||
return new NotificationCompat.Action(R.drawable.ic_hourglass_top,
|
||||
player.getContext().getString(R.string.notification_action_buffering),
|
||||
null);
|
||||
|
@ -286,42 +344,45 @@ public final class NotificationUtil {
|
|||
case NotificationConstants.PLAY_PAUSE:
|
||||
if (player.getCurrentState() == Player.STATE_COMPLETED) {
|
||||
return getAction(R.drawable.ic_replay,
|
||||
R.string.exo_controls_pause_description, ACTION_PLAY_PAUSE);
|
||||
R.string.exo_controls_pause_description, ACTION_PLAY_PAUSE, outIntentAction);
|
||||
} else if (player.isPlaying()
|
||||
|| player.getCurrentState() == Player.STATE_PREFLIGHT
|
||||
|| player.getCurrentState() == Player.STATE_BLOCKED
|
||||
|| player.getCurrentState() == Player.STATE_BUFFERING) {
|
||||
return getAction(R.drawable.exo_notification_pause,
|
||||
R.string.exo_controls_pause_description, ACTION_PLAY_PAUSE);
|
||||
R.string.exo_controls_pause_description, ACTION_PLAY_PAUSE, outIntentAction);
|
||||
} else {
|
||||
return getAction(R.drawable.exo_notification_play,
|
||||
R.string.exo_controls_play_description, ACTION_PLAY_PAUSE);
|
||||
R.string.exo_controls_play_description, ACTION_PLAY_PAUSE, outIntentAction);
|
||||
}
|
||||
|
||||
case NotificationConstants.REPEAT:
|
||||
if (player.getRepeatMode() == REPEAT_MODE_ALL) {
|
||||
return getAction(R.drawable.exo_media_action_repeat_all,
|
||||
R.string.exo_controls_repeat_all_description, ACTION_REPEAT);
|
||||
R.string.exo_controls_repeat_all_description,
|
||||
ACTION_REPEAT, outIntentAction);
|
||||
} else if (player.getRepeatMode() == REPEAT_MODE_ONE) {
|
||||
return getAction(R.drawable.exo_media_action_repeat_one,
|
||||
R.string.exo_controls_repeat_one_description, ACTION_REPEAT);
|
||||
R.string.exo_controls_repeat_one_description,
|
||||
ACTION_REPEAT, outIntentAction);
|
||||
} else /* player.getRepeatMode() == REPEAT_MODE_OFF */ {
|
||||
return getAction(R.drawable.exo_media_action_repeat_off,
|
||||
R.string.exo_controls_repeat_off_description, ACTION_REPEAT);
|
||||
R.string.exo_controls_repeat_off_description,
|
||||
ACTION_REPEAT, outIntentAction);
|
||||
}
|
||||
|
||||
case NotificationConstants.SHUFFLE:
|
||||
if (player.getPlayQueue() != null && player.getPlayQueue().isShuffled()) {
|
||||
return getAction(R.drawable.exo_controls_shuffle_on,
|
||||
R.string.exo_controls_shuffle_on_description, ACTION_SHUFFLE);
|
||||
R.string.exo_controls_shuffle_on_description, ACTION_SHUFFLE, outIntentAction);
|
||||
} else {
|
||||
return getAction(R.drawable.exo_controls_shuffle_off,
|
||||
R.string.exo_controls_shuffle_off_description, ACTION_SHUFFLE);
|
||||
R.string.exo_controls_shuffle_off_description, ACTION_SHUFFLE, outIntentAction);
|
||||
}
|
||||
|
||||
case NotificationConstants.CLOSE:
|
||||
return getAction(R.drawable.ic_close,
|
||||
R.string.close, ACTION_CLOSE);
|
||||
R.string.close, ACTION_CLOSE, outIntentAction);
|
||||
|
||||
case NotificationConstants.NOTHING:
|
||||
default:
|
||||
|
@ -332,7 +393,9 @@ public final class NotificationUtil {
|
|||
|
||||
private NotificationCompat.Action getAction(@DrawableRes final int drawable,
|
||||
@StringRes final int title,
|
||||
final String intentAction) {
|
||||
final String intentAction,
|
||||
final String[] outIntentAction) {
|
||||
outIntentAction[0] = intentAction;
|
||||
return new NotificationCompat.Action(drawable, player.getContext().getString(title),
|
||||
PendingIntentCompat.getBroadcast(player.getContext(), NOTIFICATION_ID,
|
||||
new Intent(intentAction), FLAG_UPDATE_CURRENT, false));
|
||||
|
|
Loading…
Reference in New Issue