package me.barrasso.android.volume.media.compat;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.media.AudioManager;
import android.media.MediaMetadataEditor;
import android.media.RemoteControlClient;
import android.media.RemoteController;
import android.os.Build;
import android.media.MediaMetadataRetriever;
import android.util.DisplayMetrics;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.session.PlaybackStateCompat;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
/**
* {@link me.barrasso.android.volume.media.compat.RemoteControlCompat} that utilizes the
* {@link android.media.RemoteController} API (deprecated in Android 5.0 Lollipop).
*/
@TargetApi(Build.VERSION_CODES.KITKAT)
@SuppressWarnings("deprecation")
public class RemoteControlKitKat extends RemoteControlCompat
implements RemoteController.OnClientUpdateListener {
private static MediaMetadataCompat.Builder mediaMetadata(MediaMetadataEditor editor) {
final Map<String, Integer> STRING_KEYS = new HashMap<>(10);
final Map<String, Integer> LONG_KEYS = new HashMap<>(4);
STRING_KEYS.put(MediaMetadataCompat.METADATA_KEY_ALBUM, MediaMetadataRetriever.METADATA_KEY_ALBUM);
STRING_KEYS.put(MediaMetadataCompat.METADATA_KEY_TITLE, MediaMetadataRetriever.METADATA_KEY_TITLE);
STRING_KEYS.put(MediaMetadataCompat.METADATA_KEY_ARTIST, MediaMetadataRetriever.METADATA_KEY_ARTIST);
STRING_KEYS.put(MediaMetadataCompat.METADATA_KEY_AUTHOR, MediaMetadataRetriever.METADATA_KEY_AUTHOR);
STRING_KEYS.put(MediaMetadataCompat.METADATA_KEY_COMPILATION, MediaMetadataRetriever.METADATA_KEY_COMPILATION);
STRING_KEYS.put(MediaMetadataCompat.METADATA_KEY_COMPOSER, MediaMetadataRetriever.METADATA_KEY_COMPOSER);
STRING_KEYS.put(MediaMetadataCompat.METADATA_KEY_DATE, MediaMetadataRetriever.METADATA_KEY_DATE);
STRING_KEYS.put(MediaMetadataCompat.METADATA_KEY_GENRE, MediaMetadataRetriever.METADATA_KEY_GENRE);
STRING_KEYS.put(MediaMetadataCompat.METADATA_KEY_WRITER, MediaMetadataRetriever.METADATA_KEY_WRITER);
STRING_KEYS.put(MediaMetadataCompat.METADATA_KEY_ALBUM_ARTIST, MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST);
LONG_KEYS.put(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER);
LONG_KEYS.put(MediaMetadataCompat.METADATA_KEY_DISC_NUMBER, MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER);
LONG_KEYS.put(MediaMetadataCompat.METADATA_KEY_DURATION, MediaMetadataRetriever.METADATA_KEY_DURATION);
LONG_KEYS.put(MediaMetadataCompat.METADATA_KEY_YEAR, MediaMetadataRetriever.METADATA_KEY_YEAR);
MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder();
for (Map.Entry<String, Integer> entry : STRING_KEYS.entrySet())
builder.putString(entry.getKey(), editor.getString(entry.getValue(), null));
for (Map.Entry<String, Integer> entry : LONG_KEYS.entrySet())
builder.putLong(entry.getKey(), editor.getLong(entry.getValue(), 0));
builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART,
editor.getBitmap(MediaMetadataEditor.BITMAP_KEY_ARTWORK, null));
return builder;
}
/**
* A map between {@link android.media.RemoteControlClient} flags and {@link android.media.session.PlaybackState} actions.
* @return The value to provide for {@link android.media.session.PlaybackState} for actions.
*/
private static long getPlaybackStateActions(final int transportControlFlags) {
final Map<Integer, Long> FLAG_MAP = new HashMap<>();
FLAG_MAP.put(RemoteControlClient.FLAG_KEY_MEDIA_STOP, PlaybackStateCompat.ACTION_STOP);
FLAG_MAP.put(RemoteControlClient.FLAG_KEY_MEDIA_NEXT, PlaybackStateCompat.ACTION_SKIP_TO_NEXT);
FLAG_MAP.put(RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS, PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS);
FLAG_MAP.put(RemoteControlClient.FLAG_KEY_MEDIA_PAUSE, PlaybackStateCompat.ACTION_PAUSE);
FLAG_MAP.put(RemoteControlClient.FLAG_KEY_MEDIA_FAST_FORWARD, PlaybackStateCompat.ACTION_FAST_FORWARD);
FLAG_MAP.put(RemoteControlClient.FLAG_KEY_MEDIA_REWIND, PlaybackStateCompat.ACTION_REWIND);
FLAG_MAP.put(RemoteControlClient.FLAG_KEY_MEDIA_PLAY, PlaybackStateCompat.ACTION_PLAY);
FLAG_MAP.put(RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE, PlaybackStateCompat.ACTION_PLAY_PAUSE);
FLAG_MAP.put(RemoteControlClient.FLAG_KEY_MEDIA_RATING, PlaybackStateCompat.ACTION_SET_RATING);
long actions = 0;
for (Map.Entry<Integer, Long> flags : FLAG_MAP.entrySet()) {
if ((transportControlFlags & flags.getKey()) == flags.getKey()) {
if (actions == 0)
actions = flags.getValue();
else
actions |= flags.getValue();
}
}
return actions;
}
/**
* @return A {@link android.support.v4.media.MediaMetadataCompat} built from
* a {@link android.media.MediaMetadataEditor} meant to bridge the two APIs.
*/
public static MediaMetadataCompat buildMediaMetadata(MediaMetadataEditor editor) {
return mediaMetadata(editor).build();
}
protected int mTransportControlFlags;
protected RemoteController rController;
public RemoteControlKitKat(Context context) {
super(context);
registerController();
}
// ===== API =====
@Override
public boolean isRegistered() {
return (null != rController);
}
@Override
public void release() {
if (null == rController) return;
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
audioManager.unregisterRemoteController(rController);
rController = null;
}
// == Registration ==
protected void registerController() {
AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
rController = new RemoteController(mContext, (RemoteController.OnClientUpdateListener) mContext);
try {
// This is a weird issue that needs more clarification.
audioManager.registerRemoteController(rController);
} catch (SecurityException se) {
rController = null;
}
// By default an RemoteController.OnClientUpdateListener implementation will not receive bitmaps
// for album art. Use setArtworkConfiguration(int, int) to receive images as well.
if (null != rController) {
Resources res = mContext.getResources();
DisplayMetrics dm = res.getDisplayMetrics();
final int dim = Math.max(dm.widthPixels, dm.heightPixels);
rController.setArtworkConfiguration(dim, dim);
}
}
// == Callbacks ==
@Override
public void onClientChange(boolean clearing) {
if (null == mMetadata) return;
metadataChanged(new MediaMetadataCompat.Builder(mMetadata));
}
@Override
public void onClientMetadataUpdate(RemoteController.MetadataEditor metadataEditor) {
if (null == metadataEditor) return;
metadataChanged(mediaMetadata(metadataEditor));
}
@Override
public void onClientPlaybackStateUpdate(int state, long stateChangeTimeMs, long currentPosMs, float speed) {
PlaybackStateCompat.Builder builder = new PlaybackStateCompat.Builder();
builder.setState(state, currentPosMs, speed);
playbackStateChanged(builder.build());
}
@Override
public void onClientPlaybackStateUpdate(int state) {
if (null == mPlaybackState) return;
PlaybackStateCompat.Builder builder = new PlaybackStateCompat.Builder();
builder.setState(state, rController.getEstimatedMediaPosition(), mPlaybackState.getPlaybackSpeed());
builder.setActions(getPlaybackStateActions(mTransportControlFlags));
playbackStateChanged(builder.build());
}
@Override
public void onClientTransportControlUpdate(int transportControlFlags) {
mTransportControlFlags = transportControlFlags;
PlaybackStateCompat state = getPlaybackState();
if (null == state) return;
PlaybackStateCompat.Builder builder = new PlaybackStateCompat.Builder();
builder.setState(state.getState(), rController.getEstimatedMediaPosition(), state.getPlaybackSpeed());
builder.setActions(getPlaybackStateActions(transportControlFlags));
playbackStateChanged(builder.build());
}
// == Remote Package Hack ==
protected void metadataChanged(MediaMetadataCompat.Builder builder) {
builder.putString(METADATA_KEY_PACKAGE, getPackageName());
metadataChanged(builder.build());
}
private static Method mGetRemoteControlClientPackageName;
static {
try {
mGetRemoteControlClientPackageName = RemoteController.class.getDeclaredMethod("getRemoteControlClientPackageName");
if (null != mGetRemoteControlClientPackageName)
mGetRemoteControlClientPackageName.setAccessible(true);
} catch (NoSuchMethodException e) { }
}
protected String getPackageName() {
if (null != mGetRemoteControlClientPackageName) {
try {
return (String) mGetRemoteControlClientPackageName.invoke(rController);
} catch (Throwable t) { }
}
return "";
}
}