Skip to content

Commit

Permalink
Updates for custom action support in Android 13
Browse files Browse the repository at this point in the history
Supports fast forward, rewind, and stop internally without any API changes.
Supports app defined custom actions with custom icons.
  • Loading branch information
defsub committed Feb 13, 2023
1 parent 668ede0 commit 90aa658
Show file tree
Hide file tree
Showing 8 changed files with 322 additions and 41 deletions.
3 changes: 2 additions & 1 deletion audio_service/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,15 @@ project.getTasks().withType(JavaCompile) {
apply plugin: 'com.android.library'

android {
compileSdkVersion 31
compileSdkVersion 33

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
defaultConfig {
minSdkVersion 16
targetSdkVersion 33
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
lintOptions {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,9 @@ public class AudioService extends MediaBrowserServiceCompat {
private static final int NOTIFICATION_ID = 1124;
private static final int REQUEST_CONTENT_INTENT = 1000;
public static final String NOTIFICATION_CLICK_ACTION = "com.ryanheise.audioservice.NOTIFICATION_CLICK";
public static final String MEDIA_BUTTON_REWIND_ACTION = "com.ryanheise.audioservice.MEDIA_BUTTON_REWIND";
public static final String MEDIA_BUTTON_FAST_FORWARD_ACTION = "com.ryanheise.audioservice.MEDIA_BUTTON_FAST_FORWARD_ACTION";
public static final String CUSTOM_MEDIA_BUTTON_ACTION = "com.ryanheise.audioservice.CUSTOM_MEDIA_BUTTON";
public static final String EXTRA_ACTION_CODE = "actionCode";
public static final String EXTRA_CUSTOM_ACTION = "customAction";
private static final String BROWSABLE_ROOT_ID = "root";
private static final String RECENT_ROOT_ID = "recent";
// See the comment in onMediaButtonEvent to understand how the BYPASS keycodes work.
Expand Down Expand Up @@ -436,25 +437,34 @@ NotificationCompat.Action createAction(String resource, String label, long actio
}

private boolean needCustomMediaControl(MediaControl control) {
return control.actionCode == PlaybackStateCompat.ACTION_FAST_FORWARD ||
control.actionCode == PlaybackStateCompat.ACTION_REWIND;
}

private String toCustomActionName(long actionCode) {
if (actionCode == PlaybackStateCompat.ACTION_FAST_FORWARD) {
return MEDIA_BUTTON_FAST_FORWARD_ACTION;
}
if (actionCode == PlaybackStateCompat.ACTION_REWIND) {
return MEDIA_BUTTON_REWIND_ACTION;
if (control.customAction != null && control.customAction.length() > 0) {
return true;
}
return "";

// Android 13 changes MediaControl behavior as documented here:
// https://developer.android.com/about/versions/13/behavior-changes-13
// The below actions will be added to slots 1-3, if included.
// 1 - ACTION_PLAY, ACTION_PLAY
// 2 - ACTION_SKIP_TO_PREVIOUS
// 3 - ACTION_SKIP_TO_NEXT
// Custom actions will use slots 2-5 if included.
// - ACTION_STOP
// - ACTION_FAST_FORWARD
// - ACTION_REWIND
return (Build.VERSION.SDK_INT >= 33 &&
(control.actionCode == PlaybackStateCompat.ACTION_STOP ||
control.actionCode == PlaybackStateCompat.ACTION_FAST_FORWARD ||
control.actionCode == PlaybackStateCompat.ACTION_REWIND));
}

PlaybackStateCompat.CustomAction createCustomAction(String resource, String label, long actionCode) {
int iconId = getResourceId(resource);
String action = toCustomActionName(actionCode);
PlaybackStateCompat.CustomAction createCustomAction(MediaControl action) {
int iconId = getResourceId(action.icon);
Bundle extras = new Bundle();
extras.putLong(EXTRA_ACTION_CODE, action.actionCode);
extras.putString(EXTRA_CUSTOM_ACTION, action.customAction);
PlaybackStateCompat.CustomAction.Builder builder =
new PlaybackStateCompat.CustomAction.Builder(action, label, iconId);
new PlaybackStateCompat.CustomAction.Builder(
CUSTOM_MEDIA_BUTTON_ACTION, action.label, iconId).setExtras(extras);
return builder.build();
}

Expand Down Expand Up @@ -494,12 +504,8 @@ void setState(List<MediaControl> actions, long actionBits, int[] compactActionIn
this.nativeActions.clear();
this.customActions.clear();
for (MediaControl action : actions) {
if (Build.VERSION.SDK_INT >= 33 && needCustomMediaControl(action)) {
// Android 13 changes MediaControl behavior as documented here:
// https://developer.android.com/about/versions/13/behavior-changes-13
// Generally speaking, play, pause, prev & next are handled based on state.
// Other media controls are only supported as custom actions.
customActions.add(createCustomAction(action.icon, action.label, action.actionCode));
if (needCustomMediaControl(action)) {
customActions.add(createCustomAction(action));
} else {
nativeActions.add(createAction(action.icon, action.label, action.actionCode));
}
Expand All @@ -517,10 +523,8 @@ void setState(List<MediaControl> actions, long actionBits, int[] compactActionIn
.setState(getPlaybackState(), position, speed, updateTime)
.setBufferedPosition(bufferedPosition);

if (Build.VERSION.SDK_INT >= 33) {
for (PlaybackStateCompat.CustomAction action : this.customActions) {
stateBuilder.addCustomAction(action);
}
for (PlaybackStateCompat.CustomAction action : this.customActions) {
stateBuilder.addCustomAction(action);
}

if (queueIndex != null)
Expand Down Expand Up @@ -600,7 +604,7 @@ public int getPlaybackState() {
private Notification buildNotification() {
int[] compactActionIndices = this.compactActionIndices;
if (compactActionIndices == null) {
compactActionIndices = new int[Math.min(MAX_COMPACT_ACTIONS, actions.size())];
compactActionIndices = new int[Math.min(MAX_COMPACT_ACTIONS, nativeActions.size())];
for (int i = 0; i < compactActionIndices.length; i++) compactActionIndices[i] = i;
}
NotificationCompat.Builder builder = getNotificationBuilder();
Expand Down Expand Up @@ -1065,15 +1069,24 @@ public void onSetShuffleMode(int shuffleMode) {
@Override
public void onCustomAction(String action, Bundle extras) {
if (listener == null) return;
switch (action) {
case MEDIA_BUTTON_FAST_FORWARD_ACTION:
listener.onFastForward();
return;
case MEDIA_BUTTON_REWIND_ACTION:
if (CUSTOM_MEDIA_BUTTON_ACTION.equals(action)) {
long actionCode = extras.getLong(EXTRA_ACTION_CODE, -1);
String customAction = extras.getString(EXTRA_CUSTOM_ACTION);
if (customAction != null && customAction.length() > 0) {
listener.onCustomAction(customAction, Bundle.EMPTY);
}
else if (actionCode == PlaybackStateCompat.ACTION_STOP) {
listener.onStop();
} else if (actionCode == PlaybackStateCompat.ACTION_REWIND) {
listener.onRewind();
return;
} else if (actionCode == PlaybackStateCompat.ACTION_FAST_FORWARD) {
listener.onFastForward();
} else {
listener.onCustomAction(action, extras);
}
} else {
listener.onCustomAction(action, extras);
}
listener.onCustomAction(action, extras);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -869,8 +869,9 @@ public void onMethodCall(MethodCall call, Result result) {
String resource = (String)rawControl.get("androidIcon");
String label = (String)rawControl.get("label");
long actionCode = 1 << ((Integer)rawControl.get("action"));
String customAction = (String)rawControl.get("customAction");
actionBits |= actionCode;
actions.add(new MediaControl(resource, label, actionCode));
actions.add(new MediaControl(resource, label, actionCode, customAction));
}
for (Integer rawSystemAction : rawSystemActions) {
long actionCode = 1 << rawSystemAction;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ public class MediaControl {
public final String icon;
public final String label;
public final long actionCode;
public final String customAction;

public MediaControl(String icon, String label, long actionCode) {
public MediaControl(String icon, String label, long actionCode, String customAction) {
this.icon = icon;
this.label = label;
this.actionCode = actionCode;
this.customAction = customAction;
}

@Override
Expand Down
4 changes: 2 additions & 2 deletions audio_service/example/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ apply plugin: 'com.android.application'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"

android {
compileSdkVersion 31
compileSdkVersion 33

lintOptions {
disable 'InvalidPackage'
Expand All @@ -34,7 +34,7 @@ android {
defaultConfig {
applicationId "com.ryanheise.audioserviceexample"
minSdkVersion 21
targetSdkVersion 31
targetSdkVersion 33
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
Expand Down
Loading

0 comments on commit 90aa658

Please sign in to comment.