package me.barrasso.android.volume.media; import android.app.PendingIntent; import android.content.Context; import android.graphics.Bitmap; import android.media.RemoteControlClient; import android.os.Build; import com.squareup.otto.Produce; import com.woodblockwithoutco.remotemetadataprovider.v18.media.RemoteMetadataProvider; import com.woodblockwithoutco.remotemetadataprovider.v18.media.enums.PlayState; import com.woodblockwithoutco.remotemetadataprovider.v18.media.enums.RemoteControlFeature; import com.woodblockwithoutco.remotemetadataprovider.v18.media.listeners.OnArtworkChangeListener; import com.woodblockwithoutco.remotemetadataprovider.v18.media.listeners.OnMetadataChangeListener; import com.woodblockwithoutco.remotemetadataprovider.v18.media.listeners.OnPlaybackStateChangeListener; import com.woodblockwithoutco.remotemetadataprovider.v18.media.listeners.OnRemoteControlFeaturesChangeListener; import java.util.List; import me.barrasso.android.volume.LogUtils; import static me.barrasso.android.volume.LogUtils.LOGE; import static me.barrasso.android.volume.LogUtils.LOGI; import com.squareup.otto.MainThreadBus; /** * Delegate class to deal with {@link com.woodblockwithoutco.remotemetadataprovider.v18.media.RemoteMetadataProvider}. * All methods are no-ops for Android versions other than {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2}. * All metadata updates are posted using Otto, {@link com.squareup.otto.Bus}. {@link java.lang.Throwable}s thrown * for any reason are intend to be automatically caught for easy integration. */ public class MediaProviderDelegate { // We ONLY support API 18, it's special! public static final boolean IS_V18 = (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN_MR2); public static final String TAG = LogUtils.makeLogTag(MediaProviderDelegate.class); private static MediaProviderDelegate delegate; public synchronized static MediaProviderDelegate getDelegate(Context context) { if (null == delegate) delegate = new MediaProviderDelegate(context); return delegate; } protected static boolean isPlaying(PlayState state) { switch (state) { case PLAYING: case SKIPPING_BACKWARDS: case SKIPPING_FORWARDS: case FAST_FORWARDING: return true; } return false; } private RemoteMetadataProvider remoteMetadataProvider; protected final PlaybackInfo playbackInfo = new PlaybackInfo(); protected final Metadata metadata = new Metadata(); protected MediaProviderDelegate(Context context) { if (!IS_V18) { remoteMetadataProvider = null; return; } try { remoteMetadataProvider = RemoteMetadataProvider.getInstance(context); } catch (Throwable t) { LOGE(TAG, "Could not create RemoteMetadataProvider", t); remoteMetadataProvider = null; return; } MainThreadBus.get().register(this); // Listener, if album art is requested, for retrieving album art. remoteMetadataProvider.setOnArtworkChangeListener(new OnArtworkChangeListener() { @Override public void onArtworkChanged(Bitmap artwork) { metadata.setArtwork(artwork); LOGI(TAG, "onArtworkChanged(hasArtwork=" + (artwork != null) + ')'); MainThreadBus.get().post(producePlaybackEvent()); } }); // Listener for when the music information (song, artist) has changed. remoteMetadataProvider.setOnMetadataChangeListener(new OnMetadataChangeListener() { @Override public void onMetadataChanged(String artist, String title, String album, String albumArtist, long duration) { metadata.setAlbum(album); metadata.setArtist(artist); metadata.setTitle(title); metadata.setDuration(duration); LOGI(TAG, "onMetadataChanged(" + metadata.toString() + ')'); MainThreadBus.get().post(producePlaybackEvent()); } }); // Listener for when the playback state (paused, playing, position) has changed. remoteMetadataProvider.setOnPlaybackStateChangeListener(new OnPlaybackStateChangeListener() { @Override public void onPlaybackStateChanged(PlayState playbackState, long playbackPosition, float speed) { metadata.setPlayState(isPlaying(playbackState)); playbackInfo.mCurrentPosMs = playbackPosition; playbackInfo.mSpeed = speed; playbackInfo.mState = getStateFromPlayState(playbackState); updatePackage(); LOGI(TAG, "onPlaybackStateChanged(" + playbackInfo.toString() + ')'); MainThreadBus.get().post(producePlaybackEvent()); MainThreadBus.get().post(producePlaybackPosition()); } }); // Listener for when an app informs us of new control features. remoteMetadataProvider.setOnRemoteControlFeaturesChangeListener(new OnRemoteControlFeaturesChangeListener() { @Override public void onFeaturesChanged(List<RemoteControlFeature> remoteControlFeatures) { playbackInfo.mTransportControlFlags = getTransportFlagsFromFeatureList(remoteControlFeatures); LOGI(TAG, "onFeaturesChanged(" + playbackInfo.mTransportControlFlags + ')'); updatePackage(); MainThreadBus.get().post(producePlaybackEvent()); } }); } /*package*/ static int getStateFromPlayState(PlayState playState) { switch (playState) { case BUFFERING: return RemoteControlClient.PLAYSTATE_BUFFERING; case ERROR: return RemoteControlClient.PLAYSTATE_ERROR; case FAST_FORWARDING: return RemoteControlClient.PLAYSTATE_FAST_FORWARDING; case PAUSED: return RemoteControlClient.PLAYSTATE_PAUSED; case PLAYING: return RemoteControlClient.PLAYSTATE_PLAYING; case REWINDING: return RemoteControlClient.PLAYSTATE_REWINDING; case SKIPPING_BACKWARDS: return RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS; case SKIPPING_FORWARDS: return RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS; case STOPPED: return RemoteControlClient.PLAYSTATE_STOPPED; default: return RemoteControlClient.PLAYSTATE_ERROR; } } /*package*/ static int getTransportFlagsFromFeatureList(List<RemoteControlFeature> remoteControlFeatures) { int flags = 0; if (remoteControlFeatures.contains(RemoteControlFeature.USES_FAST_FORWARD)) { int flag = RemoteControlClient.FLAG_KEY_MEDIA_FAST_FORWARD; flags = (flags == 0) ? flag : (flags | flag); } if (remoteControlFeatures.contains(RemoteControlFeature.USES_NEXT)) { int flag = RemoteControlClient.FLAG_KEY_MEDIA_NEXT; flags = (flags == 0) ? flag : (flags | flag); } if (remoteControlFeatures.contains(RemoteControlFeature.USES_PAUSE)) { int flag = RemoteControlClient.FLAG_KEY_MEDIA_PAUSE; flags = (flags == 0) ? flag : (flags | flag); } if (remoteControlFeatures.contains(RemoteControlFeature.USES_PLAY)) { int flag = RemoteControlClient.FLAG_KEY_MEDIA_PLAY; flags = (flags == 0) ? flag : (flags | flag); } if (remoteControlFeatures.contains(RemoteControlFeature.USES_PLAY_PAUSE)) { int flag = RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE; flags = (flags == 0) ? flag : (flags | flag); } if (remoteControlFeatures.contains(RemoteControlFeature.USES_PREVIOUS)) { int flag = RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS; flags = (flags == 0) ? flag : (flags | flag); } if (remoteControlFeatures.contains(RemoteControlFeature.USES_REWIND)) { int flag = RemoteControlClient.FLAG_KEY_MEDIA_REWIND; flags = (flags == 0) ? flag : (flags | flag); } if (remoteControlFeatures.contains(RemoteControlFeature.USES_STOP)) { int flag = RemoteControlClient.FLAG_KEY_MEDIA_STOP; flags = (flags == 0) ? flag : (flags | flag); } if (remoteControlFeatures.contains(RemoteControlFeature.USES_POSITIONING)) { int flag = RemoteControlClient.FLAG_KEY_MEDIA_POSITION_UPDATE; flags = (flags == 0) ? flag : (flags | flag); } return flags; } @Produce public Metadata.PlaybackPosition producePlaybackPosition() { return new Metadata.PlaybackPosition(playbackInfo.mCurrentPosMs); } @Produce public Metadata.PlaybackEvent producePlaybackEvent() { Metadata.PlaybackEvent event = new Metadata.PlaybackEvent(); event.mMetadata = metadata; event.mPlaybackInfo = playbackInfo; event.mMetadata.hasRemote(true); return event; } protected void updatePackage() { // This is an optional parameter obtained via Reflection, if possible. // Useful for obtaining information such as the app's icon and using it in the volume_3 panel. if (!IS_V18 || null == remoteMetadataProvider) return; LOGI(TAG, "updatePackage()"); PendingIntent intent = remoteMetadataProvider.getCurrentClientPendingIntent(); if (null == intent) return; playbackInfo.mRemotePackageName = intent.getCreatorPackage(); if (null != metadata) metadata.setRemotePackage(playbackInfo.mRemotePackageName); } protected boolean isAcquired = false; /** @see com.woodblockwithoutco.remotemetadataprovider.v18.media.RemoteMetadataProvider#acquireRemoteControls(int, int) */ public void acquire(final int width, final int height) { if (!IS_V18 || remoteMetadataProvider == null) return; try { LOGI(TAG, "acquire(" + width + ", " + height + ')'); remoteMetadataProvider.acquireRemoteControls(width, height); isAcquired = true; } catch (Throwable t) { LOGE(TAG, "Could not acquire IRemoteControlDisplay [" + width + ", " + height + "]", t); } } /** @see com.woodblockwithoutco.remotemetadataprovider.v18.media.RemoteMetadataProvider#dropRemoteControls(boolean) */ public void relinquish(final boolean destroy) { if (!IS_V18 || remoteMetadataProvider == null) return; try { LOGI(TAG, "relinquish(" + destroy + ')'); if (isAcquired) remoteMetadataProvider.dropRemoteControls(destroy); } catch (Throwable t) { LOGE(TAG, "Could not unregister IRemoteControlDisplay", t); } isAcquired = false; } /** @see com.woodblockwithoutco.remotemetadataprovider.v18.media.RemoteMetadataProvider#isClientActive() */ public boolean isClientActive() { if (!IS_V18 || remoteMetadataProvider == null) return false; try { LOGI(TAG, "isClientActive()"); return remoteMetadataProvider.isClientActive(); } catch (Throwable t) { LOGE(TAG, "Couldn't call RemoteMetadataProvider#isClientActive()", t); } return false; } public void destroy() { LOGI(TAG, "destroy()"); relinquish(true); metadata.clearArtwork(); metadata.recycle(); remoteMetadataProvider = null; delegate = null; // Remove static try { MainThreadBus.get().unregister(this); } catch (Throwable t) { LOGE(TAG, "Failed to unregister from Otto's EventBus."); } } }